feat: create database view on same database (#2829)

* feat: create database view on same database

* feat: switch tag between views

* fix: calendar tool bar

* fix: set layout setting

* chore: update collab rev

* fix: board layout issue

* test: add integration tests

* test: add calendar start from day test
This commit is contained in:
Nathan.fooo
2023-06-20 23:48:34 +08:00
committed by GitHub
parent 79fc7c4cfe
commit e50d708c21
92 changed files with 3006 additions and 1419 deletions

View File

@ -110,6 +110,9 @@ pub struct CalendarEventPB {
#[pb(index = 4)]
pub timestamp: i64,
#[pb(index = 5)]
pub is_scheduled: bool,
}
#[derive(Debug, Clone, Default, ProtoBuf)]

View File

@ -23,6 +23,9 @@ pub struct DatabasePB {
#[pb(index = 4)]
pub layout_type: DatabaseLayoutPB,
#[pb(index = 5)]
pub is_linked: bool,
}
#[derive(ProtoBuf, Default)]

View File

@ -7,7 +7,7 @@ use appflowy_integrate::{CollabPersistenceConfig, RocksCollabDB};
use collab::core::collab::MutexCollab;
use collab_database::database::DatabaseData;
use collab_database::user::{DatabaseCollabBuilder, UserDatabase as InnerUserDatabase};
use collab_database::views::{CreateDatabaseParams, CreateViewParams};
use collab_database::views::{CreateDatabaseParams, CreateViewParams, DatabaseLayout};
use parking_lot::Mutex;
use tokio::sync::RwLock;
@ -16,6 +16,7 @@ use flowy_task::TaskDispatcher;
use crate::entities::{DatabaseDescriptionPB, DatabaseLayoutPB, RepeatedDatabaseDescriptionPB};
use crate::services::database::{DatabaseEditor, MutexDatabase};
use crate::services::database_view::DatabaseLayoutDepsResolver;
use crate::services::share::csv::{CSVFormat, CSVImporter, ImportResult};
pub trait DatabaseUser2: Send + Sync {
@ -179,18 +180,28 @@ impl DatabaseManager2 {
Ok(())
}
/// A linked view is a view that is linked to existing database.
#[tracing::instrument(level = "trace", skip(self), err)]
pub async fn create_linked_view(
&self,
name: String,
layout: DatabaseLayoutPB,
layout: DatabaseLayout,
database_id: String,
database_view_id: String,
) -> FlowyResult<()> {
self.with_user_database(
Err(FlowyError::internal().context("Create database view failed")),
|user_database| {
let params = CreateViewParams::new(database_id, database_view_id, name, layout.into());
let mut params = CreateViewParams::new(database_id.clone(), database_view_id, name, layout);
if let Some(database) = user_database.get_database(&database_id) {
if let Some((field, layout_setting)) = DatabaseLayoutDepsResolver::new(database, layout)
.resolve_deps_when_create_database_linked_view()
{
params = params
.with_deps_fields(vec![field])
.with_layout_setting(layout_setting);
}
};
user_database.create_database_linked_view(params)?;
Ok(())
},

View File

@ -206,7 +206,7 @@ impl DatabaseEditor {
.map(|field| field.id)
.collect()
});
database.get_fields(view_id, Some(field_ids))
database.get_fields_in_view(view_id, Some(field_ids))
}
pub async fn update_field(&self, params: FieldChangesetParams) -> FlowyResult<()> {
@ -442,7 +442,7 @@ impl DatabaseEditor {
self
.database
.lock()
.create_default_field(view_id, name, field_type.into(), |field| {
.create_field_with_mut(view_id, name, field_type.into(), |field| {
field
.type_options
.insert(field_type.to_string(), type_option_data.clone());
@ -932,11 +932,12 @@ impl DatabaseEditor {
pub async fn group_by_field(&self, view_id: &str, field_id: &str) -> FlowyResult<()> {
let view = self.database_views.get_view_editor(view_id).await?;
view.v_update_grouping_field(field_id).await?;
view.v_grouping_by_field(field_id).await?;
Ok(())
}
pub async fn set_layout_setting(&self, view_id: &str, layout_setting: LayoutSettingParams) {
tracing::trace!("set_layout_setting: {:?}", layout_setting);
if let Ok(view) = self.database_views.get_view_editor(view_id).await {
let _ = view.v_set_layout_settings(layout_setting).await;
};
@ -1042,7 +1043,7 @@ impl DatabaseEditor {
.await
.ok_or_else(FlowyError::record_not_found)?;
let rows = database_view.v_get_rows().await;
let (database_id, fields) = {
let (database_id, fields, is_linked) = {
let database = self.database.lock();
let database_id = database.get_database_id();
let fields = database
@ -1051,7 +1052,8 @@ impl DatabaseEditor {
.into_iter()
.map(FieldIdPB::from)
.collect();
(database_id, fields)
let is_linked = database.is_inline_view(view_id);
(database_id, fields, is_linked)
};
let rows = rows
@ -1063,6 +1065,7 @@ impl DatabaseEditor {
fields,
rows,
layout_type: view.layout.into(),
is_linked,
})
}
@ -1082,7 +1085,7 @@ impl DatabaseEditor {
self
.database
.lock()
.get_fields(view_id, None)
.get_fields_in_view(view_id, None)
.into_iter()
.filter(|f| FieldType::from(f.field_type).is_auto_update())
.collect::<Vec<Field>>()
@ -1139,13 +1142,17 @@ struct DatabaseViewDataImpl {
}
impl DatabaseViewData for DatabaseViewDataImpl {
fn get_database(&self) -> Arc<InnerDatabase> {
self.database.lock().clone()
}
fn get_view(&self, view_id: &str) -> Fut<Option<DatabaseView>> {
let view = self.database.lock().get_view(view_id);
to_fut(async move { view })
}
fn get_fields(&self, view_id: &str, field_ids: Option<Vec<String>>) -> Fut<Vec<Arc<Field>>> {
let fields = self.database.lock().get_fields(view_id, field_ids);
let fields = self.database.lock().get_fields_in_view(view_id, field_ids);
to_fut(async move { fields.into_iter().map(Arc::new).collect() })
}
@ -1166,7 +1173,7 @@ impl DatabaseViewData for DatabaseViewDataImpl {
field_type: FieldType,
type_option_data: TypeOptionData,
) -> Fut<Field> {
let (_, field) = self.database.lock().create_default_field(
let (_, field) = self.database.lock().create_field_with_mut(
view_id,
name.to_string(),
field_type.clone().into(),

View File

@ -0,0 +1,115 @@
use std::sync::Arc;
use collab_database::database::{gen_field_id, Database};
use collab_database::fields::Field;
use collab_database::views::{DatabaseLayout, LayoutSetting};
use crate::entities::FieldType;
use crate::services::field::DateTypeOption;
use crate::services::setting::CalendarLayoutSetting;
/// When creating a database, we need to resolve the dependencies of the views. Different database
/// view has different dependencies. For example, a calendar view depends on a date field.
pub struct DatabaseLayoutDepsResolver {
pub database: Arc<Database>,
/// The new database layout.
pub database_layout: DatabaseLayout,
}
impl DatabaseLayoutDepsResolver {
pub fn new(database: Arc<Database>, database_layout: DatabaseLayout) -> Self {
Self {
database,
database_layout,
}
}
pub fn resolve_deps_when_create_database_linked_view(&self) -> Option<(Field, LayoutSetting)> {
match self.database_layout {
DatabaseLayout::Grid => None,
DatabaseLayout::Board => None,
DatabaseLayout::Calendar => {
let field = self.create_date_field();
let layout_setting: LayoutSetting = CalendarLayoutSetting::new(field.id.clone()).into();
Some((field, layout_setting))
},
}
}
/// If the new layout type is a calendar and there is not date field in the database, it will add
/// a new date field to the database and create the corresponding layout setting.
pub fn resolve_deps_when_update_layout_type(&self, view_id: &str) {
let fields = self.database.get_fields(None);
// Insert the layout setting if it's not exist
match &self.database_layout {
DatabaseLayout::Grid => {},
DatabaseLayout::Board => {},
DatabaseLayout::Calendar => {
let date_field_id = match fields
.into_iter()
.find(|field| FieldType::from(field.field_type) == FieldType::DateTime)
{
None => {
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.create_field(field);
field_id
},
Some(date_field) => date_field.id,
};
self.create_calendar_layout_setting_if_need(view_id, &date_field_id);
},
}
}
fn create_calendar_layout_setting_if_need(&self, view_id: &str, field_id: &str) {
if self
.database
.get_layout_setting::<CalendarLayoutSetting>(view_id, &self.database_layout)
.is_none()
{
let layout_setting = CalendarLayoutSetting::new(field_id.to_string());
self
.database
.insert_layout_setting(view_id, &self.database_layout, layout_setting);
}
}
fn create_date_field(&self) -> Field {
let field_type = FieldType::DateTime;
let default_date_type_option = DateTypeOption::default();
let field_id = gen_field_id();
Field::new(
field_id,
"Date".to_string(),
field_type.clone().into(),
false,
)
.with_type_option_data(field_type, default_date_type_option.into())
}
}
// pub async fn v_get_layout_settings(&self, layout_ty: &DatabaseLayout) -> LayoutSettingParams {
// let mut layout_setting = LayoutSettingParams::default();
// match layout_ty {
// DatabaseLayout::Grid => {},
// DatabaseLayout::Board => {},
// DatabaseLayout::Calendar => {
// 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 {
// let field_type = FieldType::from(field.field_type);
//
// // Check the type of field is Datetime or not
// if field_type == FieldType::DateTime {
// layout_setting.calendar = Some(calendar_setting);
// }
// }
// }
// },
// }
//
// layout_setting
// }

View File

@ -1,3 +1,9 @@
pub use layout_deps::*;
pub use notifier::*;
pub use view_editor::*;
pub use views::*;
mod layout_deps;
mod notifier;
mod view_editor;
mod view_filter;
@ -5,7 +11,3 @@ mod view_group;
mod view_sort;
mod views;
// mod trait_impl;
pub use notifier::*;
pub use view_editor::*;
pub use views::*;

View File

@ -2,7 +2,7 @@ use std::borrow::Cow;
use std::collections::HashMap;
use std::sync::Arc;
use collab_database::database::{gen_database_filter_id, gen_database_sort_id};
use collab_database::database::{gen_database_filter_id, gen_database_sort_id, Database};
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cells, Row, RowCell, RowId, RowMeta};
use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting};
@ -30,10 +30,10 @@ use crate::services::database_view::view_group::{
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, DatabaseViewChangedNotifier,
DatabaseViewChangedReceiverRunner,
notify_did_update_setting, notify_did_update_sort, DatabaseLayoutDepsResolver,
DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner,
};
use crate::services::field::{DateTypeOption, TypeOptionCellDataHandler};
use crate::services::field::TypeOptionCellDataHandler;
use crate::services::filter::{
Filter, FilterChangeset, FilterController, FilterType, UpdatedFilterType,
};
@ -44,6 +44,8 @@ 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<Database>;
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>>>;
@ -438,7 +440,7 @@ impl DatabaseViewEditor {
pub async fn v_initialize_new_group(&self, field_id: &str) -> FlowyResult<()> {
let is_grouping_field = self.is_grouping_field(field_id).await;
if !is_grouping_field {
self.v_update_grouping_field(field_id).await?;
self.v_grouping_by_field(field_id).await?;
if let Some(view) = self.delegate.get_view(&self.view_id).await {
let setting = database_view_setting_pb_from_view(view);
@ -607,7 +609,11 @@ impl DatabaseViewEditor {
// Check the type of field is Datetime or not
if field_type == FieldType::DateTime {
layout_setting.calendar = Some(calendar_setting);
} else {
tracing::warn!("The field of calendar setting is not datetime type")
}
} else {
tracing::warn!("The field of calendar setting is not exist");
}
}
},
@ -713,7 +719,7 @@ impl DatabaseViewEditor {
/// Called when a grouping field is updated.
#[tracing::instrument(level = "debug", skip_all, err)]
pub async fn v_update_grouping_field(&self, field_id: &str) -> FlowyResult<()> {
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?;
@ -770,12 +776,23 @@ impl DatabaseViewEditor {
date_field_id: date_field.id.clone(),
title,
timestamp,
is_scheduled: timestamp != 0,
})
}
pub async fn v_get_all_calendar_events(&self) -> Option<Vec<CalendarEventPB>> {
let layout_ty = DatabaseLayout::Calendar;
let calendar_setting = self.v_get_layout_settings(&layout_ty).await.calendar?;
let calendar_setting = match self.v_get_layout_settings(&layout_ty).await.calendar {
None => {
// When create a new calendar view, the calendar setting should be created
tracing::error!(
"Calendar layout setting not found in database view:{}",
self.view_id
);
return None;
},
Some(calendar_setting) => calendar_setting,
};
// Text
let primary_field = self.delegate.get_primary_field().await?;
@ -822,6 +839,7 @@ impl DatabaseViewEditor {
date_field_id: calendar_setting.field_id.clone(),
title,
timestamp,
is_scheduled: timestamp != 0,
};
events.push(event);
}
@ -829,57 +847,25 @@ impl DatabaseViewEditor {
}
#[tracing::instrument(level = "trace", skip_all)]
pub async fn v_update_layout_type(&self, layout_type: DatabaseLayout) -> FlowyResult<()> {
pub async fn v_update_layout_type(&self, new_layout_type: DatabaseLayout) -> FlowyResult<()> {
self
.delegate
.update_layout_type(&self.view_id, &layout_type);
.update_layout_type(&self.view_id, &new_layout_type);
// Update the layout type in the database might add a new field to the database. If the new
// layout type is a calendar and there is not date field in the database, it will add a new
// date field to the database and create the corresponding layout setting.
//
let fields = self.delegate.get_fields(&self.view_id, None).await;
let date_field_id = match fields
.into_iter()
.find(|field| FieldType::from(field.field_type) == FieldType::DateTime)
// using the {} brackets to denote the lifetime of the resolver. Because the DatabaseLayoutDepsResolver
// is not sync and send, so we can't pass it to the async block.
{
None => {
tracing::trace!("Create a new date field after layout type change");
let default_date_type_option = DateTypeOption::default();
let field = self
.delegate
.create_field(
&self.view_id,
"Date",
FieldType::DateTime,
default_date_type_option.into(),
)
.await;
field.id
},
Some(date_field) => date_field.id.clone(),
};
let layout_setting = self.v_get_layout_settings(&layout_type).await;
match layout_type {
DatabaseLayout::Grid => {},
DatabaseLayout::Board => {},
DatabaseLayout::Calendar => {
if layout_setting.calendar.is_none() {
let layout_setting = CalendarLayoutSetting::new(date_field_id.clone());
self
.v_set_layout_settings(LayoutSettingParams {
layout_type,
calendar: Some(layout_setting),
})
.await?;
}
},
let resolver = DatabaseLayoutDepsResolver::new(self.delegate.get_database(), new_layout_type);
resolver.resolve_deps_when_update_layout_type(&self.view_id);
}
// initialize the group controller if the current layout support grouping
*self.group_controller.write().await =
new_group_controller(self.view_id.clone(), self.delegate.clone()).await?;
let payload = DatabaseLayoutMetaPB {
view_id: self.view_id.clone(),
layout: layout_type.into(),
layout: new_layout_type.into(),
};
send_notification(&self.view_id, DatabaseNotification::DidUpdateDatabaseLayout)
.payload(payload)

View File

@ -94,7 +94,7 @@ impl DatabaseViews {
// 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_update_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)

View File

@ -1,18 +1,21 @@
use std::collections::HashMap;
use std::fmt::Formatter;
use std::marker::PhantomData;
use std::sync::Arc;
use collab_database::fields::Field;
use indexmap::IndexMap;
use serde::de::DeserializeOwned;
use serde::Serialize;
use flowy_error::{FlowyError, FlowyResult};
use lib_infra::future::Fut;
use crate::entities::{GroupChangesPB, GroupPB, InsertedGroupPB};
use crate::services::field::RowSingleCellData;
use crate::services::group::{
default_group_setting, GeneratedGroups, Group, GroupChangeset, GroupData, GroupSetting,
};
use collab_database::fields::Field;
use flowy_error::{FlowyError, FlowyResult};
use indexmap::IndexMap;
use lib_infra::future::Fut;
use serde::de::DeserializeOwned;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt::Formatter;
use std::marker::PhantomData;
use std::sync::Arc;
pub trait GroupSettingReader: Send + Sync + 'static {
fn get_group_setting(&self, view_id: &str) -> Fut<Option<Arc<GroupSetting>>>;
@ -361,10 +364,10 @@ where
})?;
if let Some(group) = update_group {
self.group_by_id.get_mut(&group.id).map(|group_data| {
if let Some(group_data) = self.group_by_id.get_mut(&group.id) {
group_data.name = group.name.clone();
group_data.is_visible = group.visible;
});
};
}
Ok(())
}

View File

@ -1,9 +1,10 @@
use crate::entities::FieldType;
use crate::services::cell::stringify_cell_data;
use collab_database::database::Database;
use indexmap::IndexMap;
use flowy_error::{FlowyError, FlowyResult};
use indexmap::IndexMap;
use crate::entities::FieldType;
use crate::services::cell::stringify_cell_data;
#[derive(Debug, Clone, Copy)]
pub enum CSVFormat {
@ -20,7 +21,7 @@ impl CSVExport {
pub fn export_database(&self, database: &Database, style: CSVFormat) -> FlowyResult<String> {
let mut wtr = csv::Writer::from_writer(vec![]);
let inline_view_id = database.get_inline_view_id();
let fields = database.get_fields(&inline_view_id, None);
let fields = database.get_fields_in_view(&inline_view_id, None);
// Write fields
let field_records = fields