From 488cf6d31835fe1d1ead76445bcd6db5bf44e330 Mon Sep 17 00:00:00 2001 From: Richard Shiue <71320345+richardshiue@users.noreply.github.com> Date: Mon, 12 Feb 2024 21:09:37 +0800 Subject: [PATCH] feat: extend functionality of CreateDatabaseParams * rename CreateViewParams to CreateDatabaseViewParams * allow creating more than one database views when using CreateDatabaseParams * get rid of DatabaseSerde in favor of DatabaseData, and include all the views inside it --- collab-database/src/database.rs | 321 ++++++--- collab-database/src/database_serde.rs | 39 -- collab-database/src/lib.rs | 1 - collab-database/src/meta/meta_map.rs | 4 +- collab-database/src/rows/row.rs | 39 +- collab-database/src/user/db_meta.rs | 15 +- collab-database/src/user/user_db.rs | 48 +- collab-database/src/views/view.rs | 92 +-- .../tests/database_test/field_setting_test.rs | 10 +- .../tests/database_test/field_test.rs | 10 +- .../tests/database_test/group_test.rs | 6 +- collab-database/tests/database_test/helper.rs | 53 +- .../tests/database_test/restore_test.rs | 641 +++++++++--------- .../tests/database_test/row_test.rs | 8 +- .../tests/database_test/sort_test.rs | 4 +- .../tests/database_test/view_test.rs | 98 ++- .../tests/user_test/async_test/flush_test.rs | 132 +--- .../tests/user_test/async_test/row_test.rs | 312 ++------- .../tests/user_test/async_test/script.rs | 260 +++++-- collab-database/tests/user_test/cell_test.rs | 16 +- .../tests/user_test/database_test.rs | 127 +++- collab-database/tests/user_test/helper.rs | 55 +- .../tests/user_test/type_option_test.rs | 9 +- collab-folder/src/view.rs | 4 + 24 files changed, 1117 insertions(+), 1187 deletions(-) delete mode 100644 collab-database/src/database_serde.rs diff --git a/collab-database/src/database.rs b/collab-database/src/database.rs index c677bbc4b..82bf22917 100644 --- a/collab-database/src/database.rs +++ b/collab-database/src/database.rs @@ -17,7 +17,6 @@ pub use tokio_stream::wrappers::WatchStream; use crate::blocks::{Block, BlockEvent}; use crate::database_observer::DatabaseNotify; -use crate::database_serde::DatabaseSerde; use crate::error::DatabaseError; use crate::fields::{Field, FieldChangeReceiver, FieldMap}; use crate::meta::MetaMap; @@ -27,7 +26,7 @@ use crate::rows::{ }; use crate::user::DatabaseCollabService; use crate::views::{ - CalculationMap, CreateDatabaseParams, CreateViewParams, CreateViewParamsValidator, + CalculationMap, CreateDatabaseParams, CreateDatabaseViewParams, CreateViewParamsValidator, DatabaseLayout, DatabaseView, DatabaseViewMeta, FieldOrder, FieldSettingsByFieldIdMap, FieldSettingsMap, FilterMap, GroupSettingMap, LayoutSetting, OrderObjectPosition, RowOrder, SortMap, ViewChangeReceiver, ViewMap, @@ -70,26 +69,49 @@ impl Database { ) -> Result { // Get or create a empty database with the given database_id let this = Self::get_or_create(¶ms.database_id, context)?; - let (rows, fields, params) = params.split(); - if !params.deps_fields.is_empty() { - tracing::warn!("The deps_fields should be empty when creating a database with inline view."); - } + + let CreateDatabaseParams { + database_id: _, + rows, + fields, + inline_view_id, + mut views, + } = params; + + let inline_view = + if let Some(index) = views.iter().position(|view| view.view_id == inline_view_id) { + views.remove(index) + } else { + return Err(DatabaseError::DatabaseViewNotExist); + }; let row_orders = this.block.create_rows(rows); - let field_orders = fields.iter().map(FieldOrder::from).collect(); + let field_orders: Vec = fields.iter().map(FieldOrder::from).collect(); this.root.with_transact_mut(|txn| { // Set the inline view id. The inline view id should not be // empty if the current database exists. - this.set_inline_view_with_txn(txn, ¶ms.view_id); + this.set_inline_view_with_txn(txn, &inline_view_id); // Insert the given fields into the database for field in fields { this.fields.insert_field_with_txn(txn, field); } - // Create a inline view - this.create_view_with_txn(txn, params, field_orders, row_orders)?; + // Create the inline view + this.create_view_with_txn(txn, inline_view, field_orders.clone(), row_orders.clone())?; + + // create the linked views + for linked_view in views { + this.create_linked_view_with_txn( + txn, + linked_view, + field_orders.clone(), + row_orders.clone(), + )?; + } + Ok::<(), DatabaseError>(()) })?; + Ok(this) } @@ -175,24 +197,26 @@ impl Database { .get_map_with_txn(txn, vec![DATABASE, METAS]) .unwrap(); + let fields = FieldMap::new( + fields, + context + .notifier + .as_ref() + .map(|notifier| notifier.field_change_tx.clone()), + ); + + let views = ViewMap::new( + views, + context + .notifier + .as_ref() + .map(|notifier| notifier.view_change_tx.clone()), + ); + let metas = MetaMap::new(metas); + (fields, views, metas) }); - let views = ViewMap::new( - views, - context - .notifier - .as_ref() - .map(|notifier| notifier.view_change_tx.clone()), - ); - let fields = FieldMap::new( - fields, - context - .notifier - .as_ref() - .as_ref() - .map(|notifier| notifier.field_change_tx.clone()), - ); - let metas = MetaMap::new(metas); + let block = Block::new( context.uid, context.db.clone(), @@ -1047,47 +1071,58 @@ impl Database { } /// Create a linked view to existing database - pub fn create_linked_view(&self, params: CreateViewParams) -> Result<(), DatabaseError> { - let mut params = CreateViewParamsValidator::validate(params)?; + pub fn create_linked_view(&self, params: CreateDatabaseViewParams) -> Result<(), DatabaseError> { self.root.with_transact_mut(|txn| { let inline_view_id = self.get_inline_view_id_with_txn(txn); let row_orders = self.views.get_row_orders_with_txn(txn, &inline_view_id); let field_orders = self.views.get_field_orders_with_txn(txn, &inline_view_id); - let (deps_fields, deps_field_settings) = params.take_deps_fields(); - - self.create_view_with_txn(txn, params, field_orders, row_orders)?; - - // After creating the view, we need to create the fields that are used in the view. - if !deps_fields.is_empty() { - tracing::trace!("create linked view with deps fields: {:?}", deps_fields); - deps_fields - .into_iter() - .zip(deps_field_settings.into_iter()) - .for_each(|(field, field_settings)| { - self.create_field_with_txn( - txn, - None, - field, - &OrderObjectPosition::default(), - &field_settings, - ); - }) - } + + self.create_linked_view_with_txn(txn, params, field_orders, row_orders)?; Ok::<(), DatabaseError>(()) })?; Ok(()) } + pub fn create_linked_view_with_txn( + &self, + txn: &mut TransactionMut, + params: CreateDatabaseViewParams, + field_orders: Vec, + row_orders: Vec, + ) -> Result<(), DatabaseError> { + let mut params = CreateViewParamsValidator::validate(params)?; + let (deps_fields, deps_field_settings) = params.take_deps_fields(); + + self.create_view_with_txn(txn, params, field_orders, row_orders)?; + + // After creating the view, we need to create the fields that are used in the view. + if !deps_fields.is_empty() { + tracing::trace!("create linked view with deps fields: {:?}", deps_fields); + deps_fields + .into_iter() + .zip(deps_field_settings) + .for_each(|(field, field_settings)| { + self.create_field_with_txn( + txn, + None, + field, + &OrderObjectPosition::default(), + &field_settings, + ); + }); + } + Ok(()) + } + /// Create a [DatabaseView] for the current database. pub fn create_view_with_txn( &self, txn: &mut TransactionMut, - params: CreateViewParams, + params: CreateDatabaseViewParams, field_orders: Vec, row_orders: Vec, ) -> Result<(), DatabaseError> { let params = CreateViewParamsValidator::validate(params)?; - let timestamp = timestamp(); let database_id = self.get_database_id_with_txn(txn); let view = DatabaseView { id: params.view_id, @@ -1096,13 +1131,13 @@ impl Database { layout: params.layout, layout_settings: params.layout_settings, filters: params.filters, - group_settings: params.groups, + group_settings: params.group_settings, sorts: params.sorts, field_settings: params.field_settings, row_orders, field_orders, - created_at: timestamp, - modified_at: timestamp, + created_at: params.created_at, + modified_at: params.modified_at, }; // tracing::trace!("create linked view with params {:?}", params); self.views.insert_view_with_txn(txn, view); @@ -1113,11 +1148,14 @@ impl Database { /// group, field setting, etc. pub fn duplicate_linked_view(&self, view_id: &str) -> Option { let view = self.views.get_view(view_id)?; - let mut duplicated_view = view.clone(); - duplicated_view.id = gen_database_view_id(); - duplicated_view.created_at = timestamp(); - duplicated_view.modified_at = timestamp(); - duplicated_view.name = format!("{}-copy", view.name); + let timestamp = timestamp(); + let duplicated_view = DatabaseView { + id: gen_database_view_id(), + name: format!("{}-copy", view.name), + created_at: timestamp, + modified_at: timestamp, + ..view + }; self.views.insert_view(duplicated_view.clone()); Some(duplicated_view) @@ -1126,13 +1164,15 @@ impl Database { /// Duplicate the row, and insert it after the original row. pub fn duplicate_row(&self, row_id: &RowId) -> Option { let row = self.block.get_row(row_id); + let timestamp = timestamp(); Some(CreateRowParams { id: gen_row_id(), cells: row.cells, height: row.height, visibility: row.visibility, row_position: OrderObjectPosition::After(row.id.into()), - timestamp: timestamp(), + created_at: timestamp, + modified_at: timestamp, }) } @@ -1157,30 +1197,22 @@ impl Database { }) } - pub fn duplicate_database(&self) -> DatabaseData { - let inline_view_id = self.get_inline_view_id(); + pub fn get_all_database_data(&self) -> DatabaseData { let txn = self.root.transact(); - let timestamp = timestamp(); - let mut view = self.views.get_view_with_txn(&txn, &inline_view_id).unwrap(); + + let database_id = self.get_database_id_with_txn(&txn); + let inline_view_id = self.get_inline_view_id_with_txn(&txn); + let views = self.views.get_all_views_with_txn(&txn); let fields = self.get_fields_in_view_with_txn(&txn, &inline_view_id, None); - let row_orders = self.views.get_row_orders_with_txn(&txn, &view.id); - let rows = self - .block - .get_rows_from_row_orders(&row_orders) - .into_iter() - .map(|row| CreateRowParams { - id: gen_row_id(), - cells: row.cells, - height: row.height, - visibility: row.visibility, - row_position: OrderObjectPosition::End, - timestamp, - }) - .collect::>(); + let rows = self.get_database_rows(); - view.id = gen_database_view_id(); - view.database_id = gen_database_id(); - DatabaseData { view, fields, rows } + DatabaseData { + database_id, + inline_view_id, + fields, + rows, + views, + } } pub fn get_view(&self, view_id: &str) -> Option { @@ -1189,8 +1221,8 @@ impl Database { } pub fn to_json_value(&self) -> JsonValue { - let database_serde = DatabaseSerde::from_database(self); - serde_json::to_value(&database_serde).unwrap() + let database_data = self.get_all_database_data(); + serde_json::to_value(&database_data).unwrap() } pub fn is_inline_view(&self, view_id: &str) -> bool { @@ -1217,7 +1249,7 @@ impl Database { pub fn set_inline_view_with_txn(&self, txn: &mut TransactionMut, view_id: &str) { tracing::trace!("Set inline view id: {}", view_id); - self.metas.set_inline_view_with_txn(txn, view_id); + self.metas.set_inline_view_id_with_txn(txn, view_id); } /// The inline view is the view that create with the database when initializing @@ -1225,32 +1257,25 @@ impl Database { let txn = self.root.transact(); // It's safe to unwrap because each database inline view id was set // when initializing the database - self.metas.get_inline_view_with_txn(&txn).unwrap() + self.metas.get_inline_view_id_with_txn(&txn).unwrap() } fn get_inline_view_id_with_txn(&self, txn: &T) -> String { // It's safe to unwrap because each database inline view id was set // when initializing the database - self.metas.get_inline_view_with_txn(txn).unwrap() + self.metas.get_inline_view_id_with_txn(txn).unwrap() } - /// Delete a view from the database and returns the deleted view ids. - /// If the view is the inline view, it will clear all the views. Otherwise, - /// just delete the view with given view id. - /// - pub fn delete_view(&self, view_id: &str) -> Vec { - if self.is_inline_view(view_id) { - self.root.with_transact_mut(|txn| { - let views = self.views.get_all_views_meta_with_txn(txn); + /// Delete a view from the database. If the view is the inline view it will clear all + /// the linked views as well. Otherwise, just delete the view with given view id. + pub fn delete_view(&self, view_id: &str) { + self.root.with_transact_mut(|txn| { + if self.get_inline_view_id_with_txn(txn) == view_id { self.views.clear_with_txn(txn); - views.into_iter().map(|view| view.id).collect() - }) - } else { - self.root.with_transact_mut(|txn| { - self.views.delete_view_with_txn(txn, view_id); - }); - vec![view_id.to_string()] - } + } else { + self.views.delete_view_with_txn(txn, view_id) + } + }) } /// Only expose this function in test env @@ -1301,15 +1326,95 @@ pub fn timestamp() -> i64 { } /// DatabaseData contains all the data of a database. -/// It's used to export and import a database. For example, duplicating a database +/// It's used when duplicating a database, or during import and export. #[derive(Clone, Serialize, Deserialize)] pub struct DatabaseData { - pub view: DatabaseView, + pub database_id: String, + pub inline_view_id: String, + pub views: Vec, pub fields: Vec, - pub rows: Vec, + pub rows: Vec, } impl DatabaseData { + /// Converts DatabaseData to CreateDatabaseParams. If `regenerate` is true, + /// the timestamps, row_ids and view_ids will all be regenerated. This is used + /// when duplicating a database. If false, these fields remain the same. + /// This is used in an importing scenario. + pub fn to_create_database_params(self, regenerate: bool) -> CreateDatabaseParams { + let (database_id, inline_view_id) = if regenerate { + (gen_database_id(), gen_database_view_id()) + } else { + (self.database_id, self.inline_view_id.clone()) + }; + + let timestamp = timestamp(); + + let create_row_params = self + .rows + .into_iter() + .map(|row| { + let (id, created_at, modified_at) = if regenerate { + (gen_row_id(), timestamp, timestamp) + } else { + (row.id, row.created_at, row.last_modified) + }; + CreateRowParams { + id, + created_at, + modified_at, + cells: row.cells, + height: row.height, + visibility: row.visibility, + row_position: OrderObjectPosition::End, + } + }) + .collect(); + + let create_view_params = self + .views + .into_iter() + .map(|view| { + let view_id = if regenerate { + if view.id == self.inline_view_id { + inline_view_id.clone() + } else { + gen_database_view_id() + } + } else { + view.id + }; + let (created_at, modified_at) = if regenerate { + (timestamp, timestamp) + } else { + (view.created_at, view.modified_at) + }; + CreateDatabaseViewParams { + database_id: database_id.clone(), + view_id, + name: view.name, + layout: view.layout, + layout_settings: view.layout_settings, + filters: view.filters, + group_settings: view.group_settings, + sorts: view.sorts, + field_settings: view.field_settings, + created_at, + modified_at, + ..Default::default() + } + }) + .collect(); + + CreateDatabaseParams { + database_id, + inline_view_id, + rows: create_row_params, + fields: self.fields, + views: create_view_params, + } + } + pub fn to_json(&self) -> Result { let s = serde_json::to_string(self)?; Ok(s) @@ -1359,7 +1464,7 @@ pub fn get_database_row_ids(collab: &Collab) -> Option> { let views = ViewMap::new(views, None); let meta = MetaMap::new(metas); - let inline_view_id = meta.get_inline_view_with_txn(&txn)?; + let inline_view_id = meta.get_inline_view_id_with_txn(&txn)?; Some( views .get_row_orders_with_txn(&txn, &inline_view_id) @@ -1376,9 +1481,9 @@ where collab.with_origin_transact_mut(|txn| { if let Some(container) = collab.get_map_with_txn(txn, vec![DATABASE, METAS]) { let map = MetaMap::new(container); - let inline_view_id = map.get_inline_view_with_txn(txn).unwrap(); + let inline_view_id = map.get_inline_view_id_with_txn(txn).unwrap(); let new_inline_view_id = f(inline_view_id); - map.set_inline_view_with_txn(txn, &new_inline_view_id); + map.set_inline_view_id_with_txn(txn, &new_inline_view_id); } }) } @@ -1412,7 +1517,7 @@ pub fn get_inline_view_id(collab: &Collab) -> Option { let txn = collab.transact(); let metas = collab.get_map_with_txn(&txn, vec![DATABASE, METAS])?; let meta = MetaMap::new(metas); - meta.get_inline_view_with_txn(&txn) + meta.get_inline_view_id_with_txn(&txn) } /// Quickly retrieve database views meta. diff --git a/collab-database/src/database_serde.rs b/collab-database/src/database_serde.rs deleted file mode 100644 index 000887c61..000000000 --- a/collab-database/src/database_serde.rs +++ /dev/null @@ -1,39 +0,0 @@ -use serde::Serialize; - -use crate::database::Database; -use crate::fields::Field; -use crate::rows::Row; -use crate::views::DatabaseView; - -#[derive(Serialize)] -pub struct DatabaseSerde { - pub views: Vec, - pub rows: Vec, - pub fields: Vec, - pub inline_view: Option, -} - -impl DatabaseSerde { - pub fn from_database(database: &Database) -> DatabaseSerde { - let txn = database.root.transact(); - let inline_view = database.metas.get_inline_view_with_txn(&txn); - let views = database.views.get_all_views_with_txn(&txn); - let fields = match &inline_view { - None => vec![], - Some(view_id) => database.get_fields_in_view_with_txn(&txn, view_id, None), - }; - - let row_orders = match &inline_view { - None => vec![], - Some(view_id) => database.views.get_row_orders_with_txn(&txn, view_id), - }; - drop(txn); - let rows = database.get_rows_from_row_orders(&row_orders); - Self { - views, - rows, - fields, - inline_view, - } - } -} diff --git a/collab-database/src/lib.rs b/collab-database/src/lib.rs index d4c97abd8..614cb395d 100644 --- a/collab-database/src/lib.rs +++ b/collab-database/src/lib.rs @@ -10,5 +10,4 @@ pub mod views; mod macros; pub mod blocks; pub mod database_observer; -mod database_serde; pub mod error; diff --git a/collab-database/src/meta/meta_map.rs b/collab-database/src/meta/meta_map.rs index 5a109d221..64a25e72c 100644 --- a/collab-database/src/meta/meta_map.rs +++ b/collab-database/src/meta/meta_map.rs @@ -14,14 +14,14 @@ impl MetaMap { } /// Set the inline view id - pub fn set_inline_view_with_txn(&self, txn: &mut TransactionMut, view_id: &str) { + pub fn set_inline_view_id_with_txn(&self, txn: &mut TransactionMut, view_id: &str) { self .container .insert_str_with_txn(txn, DATABASE_INLINE_VIEW, view_id); } /// Get the inline view id - pub fn get_inline_view_with_txn(&self, txn: &T) -> Option { + pub fn get_inline_view_id_with_txn(&self, txn: &T) -> Option { self.container.get_str_with_txn(txn, DATABASE_INLINE_VIEW) } } diff --git a/collab-database/src/rows/row.rs b/collab-database/src/rows/row.rs index 86f46e922..a6422e25d 100644 --- a/collab-database/src/rows/row.rs +++ b/collab-database/src/rows/row.rs @@ -68,7 +68,7 @@ impl DatabaseRow { .set_height(row.height) .set_visibility(row.visibility) .set_created_at(row.created_at) - .set_last_modified(row.created_at) + .set_last_modified(row.last_modified) .set_cells(row.cells); }) .done(); @@ -259,7 +259,7 @@ pub struct Row { pub height: i32, pub visibility: bool, pub created_at: i64, - pub modified_at: i64, + pub last_modified: i64, } pub enum RowMetaKey { @@ -294,7 +294,7 @@ impl Row { height: DEFAULT_ROW_HEIGHT, visibility: true, created_at: timestamp, - modified_at: timestamp, + last_modified: timestamp, } } @@ -305,7 +305,7 @@ impl Row { height: DEFAULT_ROW_HEIGHT, visibility: true, created_at: 0, - modified_at: 0, + last_modified: 0, } } @@ -512,7 +512,7 @@ pub fn row_from_map_ref(map_ref: &MapRef, _meta_ref: &MapRef, txn: & height: height as i32, visibility, created_at, - modified_at, + last_modified: modified_at, }) } @@ -524,7 +524,8 @@ pub struct CreateRowParams { pub visibility: bool, #[serde(skip)] pub row_position: OrderObjectPosition, - pub timestamp: i64, + pub created_at: i64, + pub modified_at: i64, } pub(crate) struct CreateRowParamsValidator; @@ -535,8 +536,12 @@ impl CreateRowParamsValidator { return Err(DatabaseError::InvalidRowID("row_id is empty")); } - if params.timestamp == 0 { - params.timestamp = timestamp(); + let timestamp = timestamp(); + if params.created_at == 0 { + params.created_at = timestamp; + } + if params.modified_at == 0 { + params.modified_at = timestamp; } Ok(params) @@ -547,24 +552,24 @@ impl Default for CreateRowParams { fn default() -> Self { Self { id: gen_row_id(), - cells: Default::default(), + cells: Cells::default(), height: 60, visibility: true, row_position: OrderObjectPosition::default(), - timestamp: 0, + created_at: 0, + modified_at: 0, } } } impl CreateRowParams { pub fn new(id: RowId) -> Self { + let timestamp = timestamp(); Self { id, - cells: Cells::default(), - height: 60, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: timestamp(), + created_at: timestamp, + modified_at: timestamp, + ..Default::default() } } } @@ -576,8 +581,8 @@ impl From for Row { cells: params.cells, height: params.height, visibility: params.visibility, - created_at: params.timestamp, - modified_at: params.timestamp, + created_at: params.created_at, + last_modified: params.modified_at, } } } diff --git a/collab-database/src/user/db_meta.rs b/collab-database/src/user/db_meta.rs index e30c29266..3ef4e6271 100644 --- a/collab-database/src/user/db_meta.rs +++ b/collab-database/src/user/db_meta.rs @@ -7,6 +7,7 @@ use collab::preclude::{ use std::collections::HashSet; use crate::database::timestamp; +use crate::views::CreateDatabaseParams; const DATABASES: &str = "databases"; @@ -38,12 +39,20 @@ impl DatabaseMetaList { /// Create a new [DatabaseMeta] for the given database id and view id /// use [Self::update_database] to attach more views to the existing database. /// - pub fn add_database(&self, database_id: &str, view_ids: Vec) { + pub fn add_database(&self, params: &CreateDatabaseParams) { self.array_ref.with_transact_mut(|txn| { // Use HashSet to remove duplicates - let linked_views: HashSet = view_ids.into_iter().collect(); + let mut linked_views = HashSet::new(); + linked_views.insert(params.inline_view_id.to_string()); + linked_views.extend( + params + .views + .iter() + .filter(|view| view.view_id != params.inline_view_id) + .map(|view| view.view_id.clone()), + ); let record = DatabaseMeta { - database_id: database_id.to_string(), + database_id: params.database_id.clone(), created_at: timestamp(), linked_views: linked_views.into_iter().collect(), }; diff --git a/collab-database/src/user/user_db.rs b/collab-database/src/user/user_db.rs index 99de24887..f52e88ccb 100644 --- a/collab-database/src/user/user_db.rs +++ b/collab-database/src/user/user_db.rs @@ -23,10 +23,11 @@ use crate::database_observer::DatabaseNotify; use crate::error::DatabaseError; use crate::user::db_meta::{DatabaseMeta, DatabaseMetaList}; -use crate::views::{CreateDatabaseParams, CreateViewParams, CreateViewParamsValidator}; +use crate::views::{CreateDatabaseParams, CreateDatabaseViewParams, CreateViewParamsValidator}; pub type CollabDocStateByOid = HashMap; pub type CollabFuture = Pin + Send + Sync + 'static>>; + /// Use this trait to build a [MutexCollab] for a database object including [Database], /// [DatabaseView], and [DatabaseRow]. When building a [MutexCollab], the caller can add /// different [CollabPlugin]s to the [MutexCollab] to support different features. @@ -183,6 +184,7 @@ impl WorkspaceDatabase { Some(database) => Some(database), } } + /// Return the database id with the given view id. /// Multiple views can share the same database. pub async fn get_database_with_view_id(&self, view_id: &str) -> Option> { @@ -207,7 +209,6 @@ impl WorkspaceDatabase { params: CreateDatabaseParams, ) -> Result, DatabaseError> { debug_assert!(!params.database_id.is_empty()); - debug_assert!(!params.view_id.is_empty()); // Create a [Collab] for the given database id. let collab = self.collab_for_database(¶ms.database_id, CollabDocState::default()); @@ -221,41 +222,19 @@ impl WorkspaceDatabase { }; // Add a new database record. - self - .database_meta_list() - .add_database(¶ms.database_id, vec![params.view_id.clone()]); + self.database_meta_list().add_database(¶ms); let database_id = params.database_id.clone(); - // TODO(RS): insert the first view of the database. let mutex_database = MutexDatabase::new(Database::create_with_inline_view(params, context)?); let database = Arc::new(mutex_database); self.databases.lock().put(database_id, database.clone()); Ok(database) } - pub fn track_database(&self, database_id: &str, database_view_ids: Vec) { - self - .database_meta_list() - .add_database(database_id, database_view_ids); - } - - /// Create database with the data duplicated from the given database. - /// The [DatabaseData] contains all the database data. It can be - /// used to restore the database from the backup. - pub fn create_database_with_data( - &self, - data: DatabaseData, - ) -> Result, DatabaseError> { - let DatabaseData { view, fields, rows } = data; - let params = CreateDatabaseParams::from_view(view, fields, rows); - let database = self.create_database(params)?; - Ok(database) - } - /// Create linked view that shares the same data with the inline view's database /// If the inline view is deleted, the reference view will be deleted too. pub async fn create_database_linked_view( &self, - params: CreateViewParams, + params: CreateDatabaseViewParams, ) -> Result<(), DatabaseError> { let params = CreateViewParamsValidator::validate(params)?; if let Some(database) = self.get_database(¶ms.database_id).await { @@ -353,19 +332,18 @@ impl WorkspaceDatabase { &self, view_id: &str, ) -> Result, DatabaseError> { - let DatabaseData { view, fields, rows } = self.get_database_duplicated_data(view_id).await?; - let params = CreateDatabaseParams::from_view(view, fields, rows); - let database = self.create_database(params)?; + let database_data = self.get_all_database_data(view_id).await?; + + let create_database_params = database_data.to_create_database_params(true); + let database = self.create_database(create_database_params)?; + Ok(database) } - /// Duplicate the database with the given view id. - pub async fn get_database_duplicated_data( - &self, - view_id: &str, - ) -> Result { + /// Duplicate the database with the view_id of any view in the database + pub async fn get_all_database_data(&self, view_id: &str) -> Result { if let Some(database) = self.get_database_with_view_id(view_id).await { - let data = database.lock().duplicate_database(); + let data = database.lock().get_all_database_data(); Ok(data) } else { Err(DatabaseError::DatabaseNotExist) diff --git a/collab-database/src/views/view.rs b/collab-database/src/views/view.rs index f8e89d2b3..77f5ff27f 100644 --- a/collab-database/src/views/view.rs +++ b/collab-database/src/views/view.rs @@ -47,16 +47,18 @@ pub struct DatabaseViewMeta { } #[derive(Debug, Clone, Default, Serialize, Deserialize)] -pub struct CreateViewParams { +pub struct CreateDatabaseViewParams { pub database_id: String, pub view_id: String, pub name: String, pub layout: DatabaseLayout, pub layout_settings: LayoutSettings, pub filters: Vec, - pub groups: Vec, + pub group_settings: Vec, pub sorts: Vec, pub field_settings: FieldSettingsByFieldIdMap, + pub created_at: i64, + pub modified_at: i64, /// When creating a view for a database, it might need to create a new field for the view. /// For example, if the view is calendar view, it must have a date field. @@ -67,7 +69,7 @@ pub struct CreateViewParams { pub deps_field_setting: Vec>, } -impl CreateViewParams { +impl CreateDatabaseViewParams { pub fn take_deps_fields( &mut self, ) -> (Vec, Vec>) { @@ -78,7 +80,7 @@ impl CreateViewParams { } } -impl CreateViewParams { +impl CreateDatabaseViewParams { pub fn new(database_id: String, view_id: String, name: String, layout: DatabaseLayout) -> Self { Self { database_id, @@ -100,7 +102,7 @@ impl CreateViewParams { } pub fn with_groups(mut self, groups: Vec) -> Self { - self.groups = groups; + self.group_settings = groups; self } @@ -120,10 +122,29 @@ impl CreateViewParams { } } +impl From for CreateDatabaseViewParams { + fn from(view: DatabaseView) -> Self { + Self { + database_id: view.database_id, + view_id: view.id, + name: view.name, + layout: view.layout, + filters: view.filters, + layout_settings: view.layout_settings, + group_settings: view.group_settings, + sorts: view.sorts, + field_settings: view.field_settings, + ..Default::default() + } + } +} + pub(crate) struct CreateViewParamsValidator; impl CreateViewParamsValidator { - pub(crate) fn validate(params: CreateViewParams) -> Result { + pub(crate) fn validate( + params: CreateDatabaseViewParams, + ) -> Result { if params.database_id.is_empty() { return Err(DatabaseError::InvalidDatabaseID("database_id is empty")); } @@ -139,63 +160,10 @@ impl CreateViewParamsValidator { #[derive(Debug, Clone, Default, Serialize, Deserialize)] pub struct CreateDatabaseParams { pub database_id: String, - pub view_id: String, - pub view_name: String, - pub layout: DatabaseLayout, - pub layout_settings: LayoutSettings, - pub filters: Vec, - pub groups: Vec, - pub sorts: Vec, - pub field_settings: FieldSettingsByFieldIdMap, - pub created_rows: Vec, + pub inline_view_id: String, pub fields: Vec, -} - -impl CreateDatabaseParams { - pub fn from_view(view: DatabaseView, fields: Vec, rows: Vec) -> Self { - let mut params: Self = view.into(); - params.fields = fields; - params.created_rows = rows; - params - } - - pub fn split(self) -> (Vec, Vec, CreateViewParams) { - ( - self.created_rows, - self.fields, - CreateViewParams { - database_id: self.database_id, - view_id: self.view_id, - name: self.view_name, - layout: self.layout, - layout_settings: self.layout_settings, - filters: self.filters, - groups: self.groups, - sorts: self.sorts, - field_settings: self.field_settings, - deps_fields: vec![], - deps_field_setting: vec![], - }, - ) - } -} - -impl From for CreateDatabaseParams { - fn from(view: DatabaseView) -> Self { - Self { - database_id: view.database_id, - view_id: view.id, - view_name: view.name, - layout: view.layout, - layout_settings: view.layout_settings, - filters: view.filters, - groups: view.group_settings, - sorts: view.sorts, - field_settings: view.field_settings, - created_rows: vec![], - fields: vec![], - } - } + pub rows: Vec, + pub views: Vec, } const VIEW_ID: &str = "id"; diff --git a/collab-database/tests/database_test/field_setting_test.rs b/collab-database/tests/database_test/field_setting_test.rs index 69b0de3fd..ad20caf9e 100644 --- a/collab-database/tests/database_test/field_setting_test.rs +++ b/collab-database/tests/database_test/field_setting_test.rs @@ -1,5 +1,5 @@ use collab_database::fields::Field; -use collab_database::views::{CreateViewParams, DatabaseLayout, OrderObjectPosition}; +use collab_database::views::{CreateDatabaseViewParams, DatabaseLayout, OrderObjectPosition}; use std::collections::HashMap; use crate::database_test::helper::{ @@ -11,7 +11,7 @@ use crate::helper::TestFieldSetting; #[tokio::test] async fn new_field_new_field_setting_test() { let database_test = create_database_with_default_data(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), field_settings: field_settings_for_default_database(), @@ -39,7 +39,7 @@ async fn new_field_new_field_setting_test() { #[tokio::test] async fn remove_field_remove_field_setting_test() { let database_test = create_database_with_default_data(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), field_settings: field_settings_for_default_database(), @@ -66,7 +66,7 @@ async fn update_field_setting_for_some_fields_test() { width: 100, visibility: 1, }; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), field_settings: field_settings_for_default_database(), @@ -144,7 +144,7 @@ async fn duplicate_view_duplicates_field_settings_test() { async fn new_view_requires_deps_field_test() { let database_test = create_database_with_default_data(1, "1").await; let deps_field = Field::new("f4".to_string(), "date".to_string(), 3, false); - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), layout: DatabaseLayout::Calendar, diff --git a/collab-database/tests/database_test/field_test.rs b/collab-database/tests/database_test/field_test.rs index 8973155f3..9b61429c5 100644 --- a/collab-database/tests/database_test/field_test.rs +++ b/collab-database/tests/database_test/field_test.rs @@ -1,4 +1,4 @@ -use collab_database::views::CreateViewParams; +use collab_database::views::CreateDatabaseViewParams; use collab_database::{fields::Field, views::OrderObjectPosition}; use crate::database_test::helper::{ @@ -73,7 +73,7 @@ async fn create_multiple_field_test() { #[tokio::test] async fn create_field_in_view_test() { let database_test = create_database(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), ..Default::default() @@ -148,7 +148,7 @@ async fn delete_field_in_views_test() { ); } - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), ..Default::default() @@ -165,7 +165,7 @@ async fn delete_field_in_views_test() { #[tokio::test] async fn field_order_in_view_test() { let database_test = create_database(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), ..Default::default() @@ -217,7 +217,7 @@ async fn get_field_in_order_test() { #[tokio::test] async fn move_field_test() { let database_test = create_database(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), ..Default::default() diff --git a/collab-database/tests/database_test/group_test.rs b/collab-database/tests/database_test/group_test.rs index d6fa10fa8..53b240811 100644 --- a/collab-database/tests/database_test/group_test.rs +++ b/collab-database/tests/database_test/group_test.rs @@ -1,5 +1,5 @@ use collab::core::any_map::AnyMapExtension; -use collab_database::views::{CreateViewParams, DatabaseLayout}; +use collab_database::views::{CreateDatabaseViewParams, DatabaseLayout}; use crate::database_test::helper::{create_database_with_default_data, DatabaseTest}; use crate::helper::{TestGroup, TestGroupSetting, CONTENT, GROUPS}; @@ -205,10 +205,10 @@ async fn create_database_with_two_groups() -> DatabaseTest { content: "".to_string(), }; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), - groups: vec![group_1.into(), group_2.into()], + group_settings: vec![group_1.into(), group_2.into()], layout: DatabaseLayout::Grid, ..Default::default() }; diff --git a/collab-database/tests/database_test/helper.rs b/collab-database/tests/database_test/helper.rs index 2866896c1..89efbea53 100644 --- a/collab-database/tests/database_test/helper.rs +++ b/collab-database/tests/database_test/helper.rs @@ -10,8 +10,8 @@ use collab_database::fields::Field; use collab_database::rows::{CellsBuilder, CreateRowParams}; use collab_database::user::DatabaseCollabService; use collab_database::views::{ - CreateDatabaseParams, DatabaseLayout, FieldSettingsByFieldIdMap, FieldSettingsMap, LayoutSetting, - LayoutSettings, OrderObjectPosition, + CreateDatabaseParams, CreateDatabaseViewParams, DatabaseLayout, FieldSettingsByFieldIdMap, + FieldSettingsMap, LayoutSetting, LayoutSettings, OrderObjectPosition, }; use collab_entity::CollabType; use collab_plugins::local_storage::CollabPersistenceConfig; @@ -65,8 +65,13 @@ pub async fn create_database(uid: i64, database_id: &str) -> DatabaseTest { }; let params = CreateDatabaseParams { database_id: database_id.to_string(), - view_id: "v1".to_string(), - view_name: "my first database view".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: database_id.to_string(), + view_id: "v1".to_string(), + name: "my first database view".to_string(), + ..Default::default() + }], ..Default::default() }; let database = Database::create_with_inline_view(params, context).unwrap(); @@ -99,8 +104,14 @@ pub async fn create_database_with_db( notifier: Some(DatabaseNotify::default()), }; let params = CreateDatabaseParams { - view_id: "v1".to_string(), database_id: database_id.to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: database_id.to_string(), + view_id: "v1".to_string(), + name: "my first grid".to_string(), + ..Default::default() + }], ..Default::default() }; let database = Database::create_with_inline_view(params, context).unwrap(); @@ -205,15 +216,17 @@ impl DatabaseTestBuilder { }; let params = CreateDatabaseParams { database_id: self.database_id.clone(), - view_id: self.view_id, - view_name: "my first database view".to_string(), - layout: self.layout, - layout_settings: self.layout_settings, - filters: vec![], - groups: vec![], - sorts: vec![], - field_settings: self.field_settings, - created_rows: self.rows, + inline_view_id: self.view_id.clone(), + views: vec![CreateDatabaseViewParams { + database_id: self.database_id, + view_id: self.view_id, + name: "my first database view".to_string(), + layout: self.layout, + layout_settings: self.layout_settings, + field_settings: self.field_settings, + ..Default::default() + }], + rows: self.rows, fields: self.fields, }; let database = Database::create_with_inline_view(params, context).unwrap(); @@ -235,9 +248,7 @@ pub async fn create_database_with_default_data(uid: i64, database_id: &str) -> D .insert_cell("f3", TestTextCell::from("1f3cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }; let row_2 = CreateRowParams { id: 2.into(), @@ -246,9 +257,7 @@ pub async fn create_database_with_default_data(uid: i64, database_id: &str) -> D .insert_cell("f2", TestTextCell::from("2f2cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }; let row_3 = CreateRowParams { id: 3.into(), @@ -257,9 +266,7 @@ pub async fn create_database_with_default_data(uid: i64, database_id: &str) -> D .insert_cell("f3", TestTextCell::from("3f3cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }; let database_test = create_database(uid, database_id).await; diff --git a/collab-database/tests/database_test/restore_test.rs b/collab-database/tests/database_test/restore_test.rs index 468fa17d1..24bfb235c 100644 --- a/collab-database/tests/database_test/restore_test.rs +++ b/collab-database/tests/database_test/restore_test.rs @@ -1,11 +1,8 @@ -use std::sync::Arc; - -use collab_database::database::DatabaseData; -use collab_database::rows::CreateRowParams; -use serde_json::{json, Value}; - -use assert_json_diff::assert_json_include; +use collab_database::fields::Field; +use collab_database::rows::{CreateRowParams, Row}; +use collab_database::views::{DatabaseLayout, DatabaseView}; use collab_plugins::CollabKVDB; +use serde_json::json; use crate::database_test::helper::{ create_database_with_db, restore_database_from_db, DatabaseTest, @@ -37,93 +34,62 @@ async fn restore_row_from_disk_test() { #[tokio::test] async fn restore_from_disk_test() { - let (db, database_test, expected) = create_database_with_view().await; - assert_json_include!(actual:database_test.to_json_value(), expected:expected); - // assert_json_eq!(expected, database_test.to_json_value()); + let (db, database_test) = create_database_with_db(1, "1").await; + assert_database_eq(database_test); // Restore from disk let database_test = restore_database_from_db(1, "1", db); - assert_json_include!(actual:database_test.to_json_value(), expected:expected); + assert_database_eq(database_test); } #[tokio::test] async fn restore_from_disk_with_different_database_id_test() { - let (db, _, _) = create_database_with_view().await; + let (db, _) = create_database_with_db(1, "1").await; let database_test = restore_database_from_db(1, "1", db); - assert_json_include!( - expected: json!( { - "fields": [], - "inline_view": "v1", - "rows": [], - "views": [ - { - "database_id": "1", - "field_orders": [], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "row_orders": [], - "sorts": [] - } - ] - }), - actual: database_test.to_json_value() - ); + + assert_database_eq(database_test); } #[tokio::test] async fn restore_from_disk_with_different_uid_test() { - let (db, _, _) = create_database_with_view().await; + let (db, _) = create_database_with_db(1, "1").await; let database_test = restore_database_from_db(1, "1", db); - assert_json_include!( - expected: json!( { - "fields": [], - "inline_view": "v1", - "rows": [], - "views": [ - { - "database_id": "1", - "field_orders": [], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "row_orders": [], - "sorts": [] - } - ] - }), - actual: database_test.to_json_value() - ); + + assert_database_eq(database_test); } -async fn create_database_with_view() -> (Arc, DatabaseTest, Value) { - let (db, database_test) = create_database_with_db(1, "1").await; - let expected = json!({ - "fields": [], - "inline_view": "v1", - "rows": [], - "views": [ - { - "database_id": "1", - "field_orders": [], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "row_orders": [], - "sorts": [] - } - ] - }); - (db, database_test, expected) +fn assert_database_eq(database_test: DatabaseTest) { + assert_eq!(database_test.fields.get_all_field_orders().len(), 0); + assert_eq!(database_test.get_database_rows().len(), 0); + assert_eq!(database_test.get_database_id(), "1".to_string()); + + let inline_view_id = database_test.get_inline_view_id(); + assert_eq!(inline_view_id, "v1".to_string()); + + let mut views = database_test.views.get_all_views(); + assert_eq!(views.len(), 1); + + let inline_view = views.remove( + views + .iter() + .position(|view| view.id == inline_view_id) + .unwrap(), + ); + + assert_eq!(inline_view.database_id, "1".to_string(),); + assert_eq!(inline_view.id, "v1".to_string()); + assert_eq!(inline_view.name, "my first grid".to_string()); + assert_eq!(inline_view.layout, DatabaseLayout::Grid); + assert!(inline_view.field_orders.is_empty()); + assert!(inline_view.row_orders.is_empty()); + assert!(inline_view.filters.is_empty()); + assert!(inline_view.group_settings.is_empty()); + assert!(inline_view.sorts.is_empty()); + assert!(inline_view.layout_settings.is_empty()); } const HISTORY_DOCUMENT_020: &str = "020_database"; + #[tokio::test] async fn open_020_history_database_test() { let (_cleaner, db_path) = unzip_history_database_db(HISTORY_DOCUMENT_020).unwrap(); @@ -133,266 +99,275 @@ async fn open_020_history_database_test() { "c0e69740-49f0-4790-a488-702e2750ba8d", db, ); - let data = database_test.duplicate_database(); + let mut data = database_test.get_all_database_data(); - let json_value = json!({ - "fields": [ - { - "field_type": 0, - "id": "E_50ji", - "is_primary": true, - "name": "Name", - "type_options": { - "0": { - "data": "" - } + let expected_database_id = "c0e69740-49f0-4790-a488-702e2750ba8d".to_string(); + let expected_inline_view_id = "b44b2906-4508-4532-ad9e-2cf33ceae304".to_string(); + + let expected_fields_json = json!([ + { + "field_type": 0, + "id": "E_50ji", + "is_primary": true, + "name": "Name", + "type_options": { + "0": { + "data": "" + } + }, + "visibility": true, + "width": 150 + }, + { + "field_type": 3, + "id": "8tbGTb", + "is_primary": false, + "name": "Type", + "type_options": { + "3": { + "content": "{\"options\":[{\"id\":\"jydv\",\"name\":\"3\",\"color\":\"LightPink\"},{\"id\":\"F2ew\",\"name\":\"2\",\"color\":\"Pink\"},{\"id\":\"hUJE\",\"name\":\"1\",\"color\":\"Purple\"}],\"disable_color\":false}" + } + }, + "visibility": true, + "width": 150 + }, + { + "field_type": 5, + "id": "e-5TiR", + "is_primary": false, + "name": "Done", + "type_options": { + "5": { + "is_selected": false + } + }, + "visibility": true, + "width": 150 + }, + { + "field_type": 1, + "id": "QfCqmc", + "is_primary": false, + "name": "Text", + "type_options": { + "0": { + "data": "", + "format": 0, + "name": "Number", + "scale": 0, + "symbol": "RUB" }, - "visibility": true, - "width": 150 + "1": { + "format": 1, + "name": "Number", + "scale": 0, + "symbol": "RUB" + } }, - { - "field_type": 3, - "id": "8tbGTb", - "is_primary": false, - "name": "Type", - "type_options": { - "3": { - "content": "{\"options\":[{\"id\":\"jydv\",\"name\":\"3\",\"color\":\"LightPink\"},{\"id\":\"F2ew\",\"name\":\"2\",\"color\":\"Pink\"},{\"id\":\"hUJE\",\"name\":\"1\",\"color\":\"Purple\"}],\"disable_color\":false}" - } + "visibility": true, + "width": 120 + }, + { + "field_type": 6, + "id": "vdCF8I", + "is_primary": false, + "name": "Text", + "type_options": { + "0": { + "content": "", + "data": "", + "url": "" }, - "visibility": true, - "width": 150 + "6": { + "content": "", + "url": "" + } }, - { - "field_type": 5, - "id": "e-5TiR", - "is_primary": false, - "name": "Done", - "type_options": { - "5": { - "is_selected": false - } + "visibility": true, + "width": 120 + }, + { + "field_type": 8, + "id": "9U02fU", + "is_primary": false, + "name": "Text", + "type_options": { + "0": { + "data": "", + "date_format": 3, + "field_type": 8, + "time_format": 0, + "timezone_id": "" }, - "visibility": true, - "width": 150 + "8": { + "date_format": 3, + "field_type": 8, + "time_format": 0, + "timezone_id": "" + } }, - { - "field_type": 1, - "id": "QfCqmc", - "is_primary": false, - "name": "Text", - "type_options": { - "0": { - "data": "", - "format": 0, - "name": "Number", - "scale": 0, - "symbol": "RUB" - }, - "1": { - "format": 1, - "name": "Number", - "scale": 0, - "symbol": "RUB" - } + "visibility": true, + "width": 120 + } + ]); + let expected_fields: Vec = serde_json::from_value(expected_fields_json).unwrap(); + + let expected_rows_json = json!([ + { + "cells": { + "8tbGTb": { + "created_at": 1690639663, + "data": "hUJE", + "field_type": 3, + "last_modified": 1690639663 + }, + "E_50ji": { + "created_at": 1690639669, + "data": "1", + "field_type": 0, + "last_modified": 1690639669 + }, + "QfCqmc": { + "created_at": 1690639678, + "data": "$1", + "field_type": 1, + "last_modified": 1690639678 }, - "visibility": true, - "width": 120 + "e-5TiR": { + "created_at": 1690639660, + "data": "Yes", + "field_type": 5, + "last_modified": 1690639660 + } }, - { - "field_type": 6, - "id": "vdCF8I", - "is_primary": false, - "name": "Text", - "type_options": { - "0": { - "content": "", - "data": "", - "url": "" - }, - "6": { - "content": "", - "url": "" - } + "height": 60, + "id": "bbd404d8-1319-4e4d-84fe-1052c57fe3e7", + "created_at": 1690639659, + "last_modified": 1690639678, + "visibility": true + }, + { + "cells": { + "8tbGTb": { + "created_at": 1690639665, + "data": "F2ew", + "field_type": 3, + "last_modified": 1690639665 + }, + "E_50ji": { + "created_at": 1690639669, + "data": "2", + "field_type": 0, + "last_modified": 1690639669 }, - "visibility": true, - "width": 120 + "QfCqmc": { + "created_at": 1690639679, + "data": "$2", + "field_type": 1, + "last_modified": 1690639679 + }, + "e-5TiR": { + "created_at": 1690639661, + "data": "Yes", + "field_type": 5, + "last_modified": 1690639661 + } }, - { - "field_type": 8, - "id": "9U02fU", - "is_primary": false, - "name": "Text", - "type_options": { - "0": { - "data": "", - "date_format": 3, - "field_type": 8, - "time_format": 0, - "timezone_id": "" - }, - "8": { - "date_format": 3, - "field_type": 8, - "time_format": 0, - "timezone_id": "" - } + "height": 60, + "id": "bcfe322e-6272-4ed3-a57e-09645ec1073a", + "created_at": 1690639659, + "last_modified": 1690639679, + "visibility": true + }, + { + "cells": { + "8tbGTb": { + "created_at": 1690639667, + "data": "jydv", + "field_type": 3, + "last_modified": 1690639667 + }, + "E_50ji": { + "created_at": 1690639670, + "data": "3", + "field_type": 0, + "last_modified": 1690639670 + }, + "QfCqmc": { + "created_at": 1690639679, + "data": "$3", + "field_type": 1, + "last_modified": 1690639679 }, - "visibility": true, - "width": 120 + "e-5TiR": { + "created_at": 1690639661, + "data": "Yes", + "field_type": 5, + "last_modified": 1690639661 + } + }, + "height": 60, + "id": "5d4418d2-621a-4ac5-ad05-e2c6fcc1bc79", + "created_at": 1690639659, + "last_modified": 1690639679, + "visibility": true + } + ]); + let expected_rows: Vec = serde_json::from_value(expected_rows_json).unwrap(); + + let expected_inline_view_json = json!({ + "created_at": 1690639659, + "database_id": "c0e69740-49f0-4790-a488-702e2750ba8d", + "field_orders": [ + { + "id": "E_50ji" + }, + { + "id": "8tbGTb" + }, + { + "id": "e-5TiR" + }, + { + "id": "QfCqmc" + }, + { + "id": "vdCF8I" + }, + { + "id": "9U02fU" } ], - "rows": [ + "filters": [ + { + "condition": 2, + "content": "", + "field_id": "E_50ji", + "id": "OWu470", + "ty": 0 + } + ], + "group_settings": [], + "id": "b44b2906-4508-4532-ad9e-2cf33ceae304", + "layout": 0, + "layout_settings": {}, + "modified_at": 1690639708, + "name": "Untitled", + "row_orders": [ { - "cells": { - "8tbGTb": { - "created_at": 1690639663, - "data": "hUJE", - "field_type": 3, - "last_modified": 1690639663 - }, - "E_50ji": { - "created_at": 1690639669, - "data": "1", - "field_type": 0, - "last_modified": 1690639669 - }, - "QfCqmc": { - "created_at": 1690639678, - "data": "$1", - "field_type": 1, - "last_modified": 1690639678 - }, - "e-5TiR": { - "created_at": 1690639660, - "data": "Yes", - "field_type": 5, - "last_modified": 1690639660 - } - }, "height": 60, - "id": "3a4bcc31-6f6d-46eb-8040-20d228d9f6ca", - "timestamp": 1690641126, - "visibility": true + "id": "bbd404d8-1319-4e4d-84fe-1052c57fe3e7" }, { - "cells": { - "8tbGTb": { - "created_at": 1690639665, - "data": "F2ew", - "field_type": 3, - "last_modified": 1690639665 - }, - "E_50ji": { - "created_at": 1690639669, - "data": "2", - "field_type": 0, - "last_modified": 1690639669 - }, - "QfCqmc": { - "created_at": 1690639679, - "data": "$2", - "field_type": 1, - "last_modified": 1690639679 - }, - "e-5TiR": { - "created_at": 1690639661, - "data": "Yes", - "field_type": 5, - "last_modified": 1690639661 - } - }, "height": 60, - "id": "1460c28e-d3ad-4260-8170-b7affb5ec8dd", - "timestamp": 1690641126, - "visibility": true + "id": "bcfe322e-6272-4ed3-a57e-09645ec1073a" }, { - "cells": { - "8tbGTb": { - "created_at": 1690639667, - "data": "jydv", - "field_type": 3, - "last_modified": 1690639667 - }, - "E_50ji": { - "created_at": 1690639670, - "data": "3", - "field_type": 0, - "last_modified": 1690639670 - }, - "QfCqmc": { - "created_at": 1690639679, - "data": "$3", - "field_type": 1, - "last_modified": 1690639679 - }, - "e-5TiR": { - "created_at": 1690639661, - "data": "Yes", - "field_type": 5, - "last_modified": 1690639661 - } - }, "height": 60, - "id": "981a4e66-1506-483f-9c4d-691bd16feeb4", - "timestamp": 1690641126, - "visibility": true + "id": "5d4418d2-621a-4ac5-ad05-e2c6fcc1bc79" } ], - "view": { - "created_at": 1690639659, - "database_id": "1b176b8a-a210-4dc6-887b-8fb08d39e621", - "field_orders": [ - { - "id": "E_50ji" - }, - { - "id": "8tbGTb" - }, - { - "id": "e-5TiR" - }, - { - "id": "QfCqmc" - }, - { - "id": "vdCF8I" - }, - { - "id": "9U02fU" - } - ], - "filters": [ - { - "condition": 2, - "content": "", - "field_id": "E_50ji", - "id": "OWu470", - "ty": 0 - } - ], - "group_settings": [], - "id": "v:pwLq8L", - "layout": 0, - "layout_settings": {}, - "modified_at": 1690639708, - "name": "Untitled", - "row_orders": [ - { - "height": 60, - "id": "bbd404d8-1319-4e4d-84fe-1052c57fe3e7" - }, - { - "height": 60, - "id": "bcfe322e-6272-4ed3-a57e-09645ec1073a" - }, - { - "height": 60, - "id": "5d4418d2-621a-4ac5-ad05-e2c6fcc1bc79" - } - ], - "sorts": [ + "sorts": [ { "condition": 0, "field_id": "E_50ji", @@ -401,31 +376,23 @@ async fn open_020_history_database_test() { } ], "field_settings": {} - } }); - let expected_data: DatabaseData = serde_json::from_value(json_value).unwrap(); - assert_eq!(data.rows.len(), expected_data.rows.len()); - assert_eq!(data.fields.len(), expected_data.fields.len()); - assert_eq!(data.view.name, expected_data.view.name); - assert_eq!(data.view.layout, expected_data.view.layout); - assert_eq!( - data.view.layout_settings, - expected_data.view.layout_settings - ); - assert_eq!(data.view.filters.len(), expected_data.view.filters.len()); - assert_eq!(data.view.sorts.len(), expected_data.view.sorts.len()); - assert_eq!( - data.view.group_settings.len(), - expected_data.view.group_settings.len() - ); - assert_eq!( - data.view.field_orders.len(), - expected_data.view.field_orders.len() - ); - assert_eq!( - data.view.row_orders.len(), - expected_data.view.row_orders.len() + let expected_inline_view: DatabaseView = + serde_json::from_value(expected_inline_view_json).unwrap(); + + assert_eq!(data.database_id, expected_database_id); + assert_eq!(data.inline_view_id, expected_inline_view_id); + assert_eq!(data.views.len(), 1); + + assert_eq!(data.rows, expected_rows); + assert_eq!(data.fields, expected_fields); + + let inline_view = data.views.remove( + data + .views + .iter() + .position(|view| view.id == data.inline_view_id) + .unwrap(), ); - assert_eq!(data.view.modified_at, expected_data.view.modified_at); - assert_eq!(data.view.created_at, expected_data.view.created_at); + assert_eq!(inline_view, expected_inline_view); } diff --git a/collab-database/tests/database_test/row_test.rs b/collab-database/tests/database_test/row_test.rs index 4e1f1edb5..8c70d98b0 100644 --- a/collab-database/tests/database_test/row_test.rs +++ b/collab-database/tests/database_test/row_test.rs @@ -1,6 +1,6 @@ use collab_database::database::gen_row_id; use collab_database::rows::{meta_id_from_row_id, CreateRowParams, RowId, RowMetaKey}; -use collab_database::views::{CreateViewParams, OrderObjectPosition}; +use collab_database::views::{CreateDatabaseViewParams, OrderObjectPosition}; use uuid::Uuid; use crate::database_test::helper::{create_database, create_database_with_default_data}; @@ -8,7 +8,7 @@ use crate::database_test::helper::{create_database, create_database_with_default #[tokio::test] async fn create_row_shared_by_two_view_test() { let database_test = create_database(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), ..Default::default() @@ -32,7 +32,7 @@ async fn create_row_shared_by_two_view_test() { #[tokio::test] async fn delete_row_shared_by_two_view_test() { let database_test = create_database(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), ..Default::default() @@ -83,7 +83,7 @@ async fn move_row_in_view_test() { #[tokio::test] async fn move_row_in_views_test() { let database_test = create_database_with_default_data(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v2".to_string(), ..Default::default() diff --git a/collab-database/tests/database_test/sort_test.rs b/collab-database/tests/database_test/sort_test.rs index eb49c988a..f33919ffb 100644 --- a/collab-database/tests/database_test/sort_test.rs +++ b/collab-database/tests/database_test/sort_test.rs @@ -1,6 +1,6 @@ use crate::database_test::helper::{create_database_with_default_data, DatabaseTest}; use crate::helper::{SortCondition, TestSort}; -use collab_database::views::{CreateViewParams, DatabaseLayout}; +use collab_database::views::{CreateDatabaseViewParams, DatabaseLayout}; #[tokio::test] async fn create_database_view_with_sort_test() { @@ -104,7 +104,7 @@ async fn create_database_with_two_sorts() -> DatabaseTest { condition: SortCondition::Descending, }; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), sorts: vec![sort_1.into(), sort_2.into()], diff --git a/collab-database/tests/database_test/view_test.rs b/collab-database/tests/database_test/view_test.rs index 4882da527..d9d9626a2 100644 --- a/collab-database/tests/database_test/view_test.rs +++ b/collab-database/tests/database_test/view_test.rs @@ -1,16 +1,13 @@ -use collab_database::database::{ - gen_row_id, get_database_views_meta, get_inline_view_id, DatabaseData, -}; +use collab_database::database::{gen_row_id, DatabaseData}; use collab_database::fields::Field; use collab_database::rows::CreateRowParams; use collab_database::views::{ - CreateViewParams, DatabaseLayout, LayoutSettingBuilder, OrderObjectPosition, + CreateDatabaseViewParams, DatabaseLayout, LayoutSettingBuilder, OrderObjectPosition, }; use nanoid::nanoid; -use serde_json::json; use std::sync::Arc; -use assert_json_diff::{assert_json_eq, assert_json_include}; +use assert_json_diff::assert_json_eq; use collab::preclude::Any; use crate::database_test::helper::{ @@ -20,39 +17,26 @@ use crate::helper::TestFilter; #[tokio::test] async fn create_initial_database_test() { - let test = create_database(1, "1").await; - assert_json_include!( - expected: json!( { - "fields": [], - "inline_view": "v1", - "rows": [], - "views": [ - { - "database_id": "1", - "field_orders": [], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "row_orders": [], - "sorts": [] - } - ] - }), - actual: test.to_json_value() - ); + let database_test = create_database(1, "1").await; + assert_eq!(database_test.fields.get_all_field_orders().len(), 0); + assert_eq!(database_test.get_database_rows().len(), 0); + assert_eq!(database_test.get_database_id(), "1".to_string()); - let inline_view_id = get_inline_view_id(&test.database.get_collab().lock()).unwrap(); - assert_eq!(inline_view_id, test.database.get_inline_view_id()); - assert_eq!(inline_view_id, "v1"); + let inline_view_id = database_test.get_inline_view_id(); + assert_eq!(inline_view_id, "v1".to_string()); - let view_metas = get_database_views_meta(&test.database.get_collab().lock()); - let view_meta = view_metas - .iter() - .find(|view| view.id == inline_view_id) - .unwrap(); - assert_eq!(view_meta.name, "my first database view"); + let mut views = database_test.views.get_all_views(); + assert_eq!(views.len(), 1); + + let inline_view = views.remove( + views + .iter() + .position(|view| view.id == inline_view_id) + .unwrap(), + ); + + assert_eq!(inline_view.database_id, "1".to_string(),); + assert_eq!(inline_view.name, "my first database view".to_string()); } #[tokio::test] @@ -64,17 +48,18 @@ async fn create_database_with_single_view_test() { } #[tokio::test] -async fn get_database_view_description_test() { +async fn get_database_views_meta_test() { let database_test = create_database_with_default_data(1, "1").await; let views = database_test.get_all_database_views_meta(); assert_eq!(views.len(), 1); - assert_eq!(views[0].name, "my first database view"); + let view = database_test.get_view("v1").unwrap(); + assert_eq!(view.name, "my first database view"); } #[tokio::test] async fn create_same_database_view_twice_test() { let database_test = create_database_with_default_data(1, "1").await; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), name: "my second grid".to_string(), @@ -142,7 +127,7 @@ async fn create_database_view_with_filter_test() { content: "".to_string(), }; - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), name: "my first grid".to_string(), @@ -171,7 +156,7 @@ async fn create_database_view_with_layout_setting_test() { .insert_any("2", "abc") .build(); - let params = CreateViewParams { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: "v1".to_string(), name: "my first grid".to_string(), @@ -191,10 +176,10 @@ async fn create_database_view_with_layout_setting_test() { } #[tokio::test] -async fn delete_inline_database_view_test() { +async fn delete_database_view_test() { let database_test = create_database_with_default_data(1, "1").await; - for i in 0..3 { - let params = CreateViewParams { + for i in 2..5 { + let params = CreateDatabaseViewParams { database_id: "1".to_string(), view_id: format!("v{}", i), ..Default::default() @@ -203,33 +188,42 @@ async fn delete_inline_database_view_test() { } let views = database_test.views.get_all_views(); - let view_id = views[1].id.clone(); - assert_eq!(views.len(), 3); + assert_eq!(views.len(), 4); - database_test.views.delete_view(&view_id); + let deleted_view_id = "v3".to_string(); + database_test.views.delete_view(&deleted_view_id); let views = database_test .views .get_all_views() .iter() .map(|view| view.id.clone()) .collect::>(); - assert_eq!(views.len(), 2); - assert!(!views.contains(&view_id)); + assert_eq!(views.len(), 3); + assert!(!views.contains(&deleted_view_id)); } #[tokio::test] async fn duplicate_database_view_test() { let database_test = create_database_with_default_data(1, "1").await; - database_test.duplicate_linked_view("v1"); + + let views = database_test.views.get_all_views(); + assert_eq!(views.len(), 1); + + let view = database_test.get_view("v1").unwrap(); + let duplicated_view = database_test.duplicate_linked_view("v1").unwrap(); let views = database_test.views.get_all_views(); assert_eq!(views.len(), 2); + + assert_eq!(duplicated_view.name, format!("{}-copy", view.name)); + assert_ne!(view.id, duplicated_view.id); + // modified and created time should also be different but the test completes within one second. } #[tokio::test] async fn duplicate_database_data_serde_test() { let database_test = create_database_with_default_data(1, "1").await; - let duplicated_database = database_test.duplicate_database(); + let duplicated_database = database_test.get_all_database_data(); let json = duplicated_database.to_json().unwrap(); let duplicated_database2 = DatabaseData::from_json(&json).unwrap(); diff --git a/collab-database/tests/user_test/async_test/flush_test.rs b/collab-database/tests/user_test/async_test/flush_test.rs index 64a13851c..ce42a9379 100644 --- a/collab-database/tests/user_test/async_test/flush_test.rs +++ b/collab-database/tests/user_test/async_test/flush_test.rs @@ -1,7 +1,8 @@ use collab_plugins::local_storage::CollabPersistenceConfig; -use serde_json::{json, Value}; -use crate::user_test::async_test::script::{create_database, database_test, DatabaseScript::*}; +use crate::user_test::async_test::script::{ + create_database, database_test, expected_fields, expected_rows, expected_view, DatabaseScript::*, +}; #[tokio::test] async fn flush_doc_test() { @@ -16,7 +17,9 @@ async fn flush_doc_test() { }, AssertDatabase { database_id: "d1".to_string(), - expected: expect(), + expected_fields: expected_fields(), + expected_rows: expected_rows(), + expected_view: expected_view(), }, ]) .await; @@ -28,127 +31,10 @@ async fn flush_doc_test() { }, AssertDatabase { database_id: "d1".to_string(), - expected: expect(), + expected_fields: expected_fields(), + expected_rows: expected_rows(), + expected_view: expected_view(), }, ]) .await; } - -fn expect() -> Value { - json!( { - "fields": [ - { - "field_type": 0, - "id": "f1", - "is_primary": true, - "name": "text field", - "type_options": {}, - "visibility": true, - "width": 120 - }, - { - "field_type": 2, - "id": "f2", - "is_primary": true, - "name": "single select field", - "type_options": {}, - "visibility": true, - "width": 120 - }, - { - "field_type": 1, - "id": "f3", - "is_primary": true, - "name": "checkbox field", - "type_options": {}, - "visibility": true, - "width": 120 - } - ], - "inline_view": "v1", - "rows": [ - { - "cells": { - "f1": { - "data": "1f1cell" - }, - "f2": { - "data": "1f2cell" - }, - "f3": { - "data": "1f3cell" - } - }, - "created_at": 0, - "height": 0, - "id": "1", - "visibility": true - }, - { - "cells": { - "f1": { - "data": "2f1cell" - }, - "f2": { - "data": "2f2cell" - } - }, - "created_at": 0, - "height": 0, - "id": "2", - "visibility": true - }, - { - "cells": { - "f1": { - "data": "3f1cell" - }, - "f3": { - "data": "3f3cell" - } - }, - "created_at": 0, - "height": 0, - "id": "3", - "visibility": true - } - ], - "views": [ - { - "database_id": "d1", - "field_orders": [ - { - "id": "f1" - }, - { - "id": "f2" - }, - { - "id": "f3" - } - ], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "name": "my first database", - "row_orders": [ - { - "height": 0, - "id": "1" - }, - { - "height": 0, - "id": "2" - }, - { - "height": 0, - "id": "3" - } - ], - "sorts": [] - } - ] - }) -} diff --git a/collab-database/tests/user_test/async_test/row_test.rs b/collab-database/tests/user_test/async_test/row_test.rs index 3818c00ce..f42902f6c 100644 --- a/collab-database/tests/user_test/async_test/row_test.rs +++ b/collab-database/tests/user_test/async_test/row_test.rs @@ -1,6 +1,6 @@ +use collab_database::database::timestamp; use collab_database::rows::CreateRowParams; use collab_database::rows::{CellsBuilder, RowId}; -use collab_database::views::OrderObjectPosition; use serde_json::{json, Value}; use collab_plugins::local_storage::CollabPersistenceConfig; @@ -8,7 +8,9 @@ use futures::stream::FuturesUnordered; use futures::StreamExt; use crate::helper::TestTextCell; -use crate::user_test::async_test::script::{create_database, database_test, DatabaseScript}; +use crate::user_test::async_test::script::{ + create_database, database_test, expected_fields, expected_rows, expected_view, DatabaseScript, +}; #[tokio::test] async fn edit_row_test() { @@ -53,9 +55,14 @@ async fn edit_row_test() { handles.push(handle); } while handles.next().await.is_some() {} + let timestamp = timestamp(); + + let mut expected_rows = expected_rows(); + expected_rows[0]["cells"]["f1"]["data"] = Value::String("hello world".to_string()); + expected_rows[0]["last_modified"] = Value::Number(timestamp.into()); + let mut expected_view = expected_view(); + expected_view["database_id"] = Value::String(database_id.clone()); - let mut expected = edit_row_expected(); - expected["rows"][0]["cells"]["f1"]["data"] = Value::String("hello world".to_string()); test .run_scripts(vec![ DatabaseScript::IsExist { @@ -64,7 +71,9 @@ async fn edit_row_test() { }, DatabaseScript::AssertDatabaseInDisk { database_id: database_id.clone(), - expected, + expected_fields: expected_fields(), + expected_rows, + expected_view, }, DatabaseScript::AssertNumOfUpdates { oid: database_id, @@ -100,18 +109,39 @@ async fn create_row_test() { cells: Default::default(), height: 0, visibility: false, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }, }); } cloned_test.run_scripts(scripts).await; - let mut expected = create_row_test_expected(); - expected["views"][0]["database_id"] = Value::String(database_id.clone()); + let timestamp = timestamp(); + + let mut expected_rows = expected_rows(); + expected_rows.as_array_mut().unwrap().push(json!({ + "cells": {}, + "created_at": timestamp, + "last_modified": timestamp, + "height": 0, + "id": "4", + "visibility": false + })); + let mut expected_view = expected_view(); + expected_view["database_id"] = Value::String(database_id.clone()); + expected_view["modified_at"] = Value::Number(timestamp.into()); + expected_view["row_orders"] + .as_array_mut() + .unwrap() + .push(json!({ + "height": 0, + "id": "4" + })); + cloned_test .run_scripts(vec![DatabaseScript::AssertDatabaseInDisk { - database_id, - expected, + database_id: database_id.clone(), + expected_fields: expected_fields(), + expected_rows, + expected_view, }]) .await; }); @@ -119,263 +149,3 @@ async fn create_row_test() { } while handles.next().await.is_some() {} } - -fn edit_row_expected() -> Value { - json!({ - "fields": [ - { - "field_type": 0, - "id": "f1", - "is_primary": true, - "name": "text field", - "type_options": {}, - "visibility": true, - "width": 120 - }, - { - "field_type": 2, - "id": "f2", - "is_primary": true, - "name": "single select field", - "type_options": {}, - "visibility": true, - "width": 120 - }, - { - "field_type": 1, - "id": "f3", - "is_primary": true, - "name": "checkbox field", - "type_options": {}, - "visibility": true, - "width": 120 - } - ], - "inline_view": "v1", - "rows": [ - { - "cells": { - "f1": { - "data": "1f1cell" - }, - "f2": { - "data": "1f2cell" - }, - "f3": { - "data": "1f3cell" - } - }, - "created_at": 0, - "height": 0, - "id": "1", - "visibility": true - }, - { - "cells": { - "f1": { - "data": "2f1cell" - }, - "f2": { - "data": "2f2cell" - } - }, - "created_at": 0, - "height": 0, - "id": "2", - "visibility": true - }, - { - "cells": { - "f1": { - "data": "3f1cell" - }, - "f3": { - "data": "3f3cell" - } - }, - "created_at": 0, - "height": 0, - "id": "3", - "visibility": true - } - ], - "views": [ - { - "database_id": "d2", - "field_orders": [ - { - "id": "f1" - }, - { - "id": "f2" - }, - { - "id": "f3" - } - ], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "name": "my first database", - "row_orders": [ - { - "height": 0, - "id": "1" - }, - { - "height": 0, - "id": "2" - }, - { - "height": 0, - "id": "3" - } - ], - "sorts": [] - } - ] - }) -} -fn create_row_test_expected() -> Value { - json!( - { - "fields": [ - { - "field_type": 0, - "id": "f1", - "is_primary": true, - "name": "text field", - "type_options": {}, - "visibility": true, - "width": 120 - }, - { - "field_type": 2, - "id": "f2", - "is_primary": true, - "name": "single select field", - "type_options": {}, - "visibility": true, - "width": 120 - }, - { - "field_type": 1, - "id": "f3", - "is_primary": true, - "name": "checkbox field", - "type_options": {}, - "visibility": true, - "width": 120 - } - ], - "inline_view": "v1", - "rows": [ - { - "block_id": 1, - "cells": { - "f1": { - "data": "1f1cell" - }, - "f2": { - "data": "1f2cell" - }, - "f3": { - "data": "1f3cell" - } - }, - "created_at": 0, - "height": 60, - "id": "1", - "visibility": true - }, - { - "block_id": 2, - "cells": { - "f1": { - "data": "2f1cell" - }, - "f2": { - "data": "2f2cell" - } - }, - "created_at": 0, - "height": 60, - "id": "2", - "visibility": true - }, - { - "block_id": 3, - "cells": { - "f1": { - "data": "3f1cell" - }, - "f3": { - "data": "3f3cell" - } - }, - "created_at": 0, - "height": 60, - "id": "3", - "visibility": true - }, - { - "block_id": 4, - "cells": {}, - "created_at": 0, - "height": 60, - "id": "4", - "visibility": false - } - ], - "views": [ - { - "created_at": 0, - "database_id": "d2", - "field_orders": [ - { - "id": "f1" - }, - { - "id": "f2" - }, - { - "id": "f3" - } - ], - "filters": [], - "group_settings": [], - "id": "v1", - "layout": 0, - "layout_settings": {}, - "modified_at": 0, - "name": "my first database", - "row_orders": [ - { - "block_id": 1, - "height": 0, - "id": "1" - }, - { - "block_id": 2, - "height": 0, - "id": "2" - }, - { - "block_id": 3, - "height": 0, - "id": "3" - }, - { - "block_id": 4, - "height": 0, - "id": "4" - } - ], - "sorts": [] - } - ] - } - ) -} diff --git a/collab-database/tests/user_test/async_test/script.rs b/collab-database/tests/user_test/async_test/script.rs index d671bed1d..e8f5b506f 100644 --- a/collab-database/tests/user_test/async_test/script.rs +++ b/collab-database/tests/user_test/async_test/script.rs @@ -1,19 +1,16 @@ -#![allow(clippy::all)] - use std::path::PathBuf; use std::sync::Arc; -use collab_database::database::DatabaseData; use collab_database::fields::Field; -use collab_database::rows::CreateRowParams; use collab_database::rows::{Cells, CellsBuilder, RowId}; +use collab_database::rows::{CreateRowParams, Row}; use collab_database::user::WorkspaceDatabase; -use collab_database::views::{CreateDatabaseParams, OrderObjectPosition}; +use collab_database::views::{CreateDatabaseParams, CreateDatabaseViewParams, DatabaseView}; use collab_plugins::local_storage::kv::doc::CollabKVAction; use collab_plugins::local_storage::kv::KVTransactionDB; use collab_plugins::local_storage::CollabPersistenceConfig; use collab_plugins::CollabKVDB; -use serde_json::Value; +use serde_json::{json, Value}; use tempfile::TempDir; use crate::database_test::helper::field_settings_for_default_database; @@ -41,11 +38,15 @@ pub enum DatabaseScript { }, AssertDatabaseInDisk { database_id: String, - expected: Value, + expected_fields: Value, + expected_rows: Value, + expected_view: Value, }, AssertDatabase { database_id: String, - expected: Value, + expected_fields: Value, + expected_rows: Value, + expected_view: Value, }, AssertNumOfUpdates { oid: String, @@ -84,17 +85,6 @@ impl DatabaseTest { } } - #[allow(dead_code)] - pub async fn get_database_data(&self, database_id: &str) -> DatabaseData { - let database = self - .workspace_database - .get_database(database_id) - .await - .unwrap(); - let duplicated_database = database.lock().duplicate_database(); - duplicated_database - } - pub async fn run_scripts(&mut self, scripts: Vec) { let mut handles = vec![]; for script in scripts { @@ -156,21 +146,62 @@ pub async fn run_script( }, DatabaseScript::AssertDatabaseInDisk { database_id, - expected, + expected_fields, + expected_rows, + expected_view, } => { let w_database = workspace_database_with_db(1, Arc::downgrade(&db), Some(config.clone())).await; let database = w_database.get_database(&database_id).await.unwrap(); - let actual = database.lock().to_json_value(); - assert_json_diff::assert_json_include!(actual: actual, expected: expected); + let database_data = database.lock().get_all_database_data(); + let view = database.lock().get_view("v1").unwrap(); + + assert_eq!( + database_data.rows, + serde_json::from_value::>(expected_rows).unwrap() + ); + assert_eq!( + database_data.fields, + serde_json::from_value::>(expected_fields).unwrap() + ); + assert_eq!( + view, + serde_json::from_value::(expected_view).unwrap() + ); }, DatabaseScript::AssertDatabase { database_id, - expected, + expected_fields, + expected_rows, + expected_view, } => { - let database = workspace_database.get_database(&database_id).await.unwrap(); - let actual = database.lock().to_json_value(); - assert_json_diff::assert_json_include!(actual: actual, expected: expected); + let database_data = workspace_database + .get_database(&database_id) + .await + .unwrap() + .lock() + .get_all_database_data(); + + let view = workspace_database + .get_database(&database_id) + .await + .unwrap() + .lock() + .get_view("v1") + .unwrap(); + + assert_eq!( + database_data.rows, + serde_json::from_value::>(expected_rows).unwrap() + ); + assert_eq!( + database_data.fields, + serde_json::from_value::>(expected_fields).unwrap() + ); + assert_eq!( + view, + serde_json::from_value::(expected_view).unwrap() + ); }, DatabaseScript::IsExist { oid: database_id, @@ -191,7 +222,7 @@ pub async fn run_script( } } -pub fn create_database(database_id: &str) -> CreateDatabaseParams { +pub(crate) fn create_database(database_id: &str) -> CreateDatabaseParams { let row_1 = CreateRowParams { id: 1.into(), cells: CellsBuilder::new() @@ -200,9 +231,9 @@ pub fn create_database(database_id: &str) -> CreateDatabaseParams { .insert_cell("f3", TestTextCell::from("1f3cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + created_at: 1703772730, + modified_at: 1703772762, + ..Default::default() }; let row_2 = CreateRowParams { id: 2.into(), @@ -211,9 +242,9 @@ pub fn create_database(database_id: &str) -> CreateDatabaseParams { .insert_cell("f2", TestTextCell::from("2f2cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + created_at: 1703772730, + modified_at: 1703772762, + ..Default::default() }; let row_3 = CreateRowParams { id: 3.into(), @@ -222,9 +253,9 @@ pub fn create_database(database_id: &str) -> CreateDatabaseParams { .insert_cell("f3", TestTextCell::from("3f3cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + created_at: 1703772730, + modified_at: 1703772762, + ..Default::default() }; let field_1 = Field::new("f1".to_string(), "text field".to_string(), 0, true); let field_2 = Field::new("f2".to_string(), "single select field".to_string(), 2, true); @@ -234,15 +265,154 @@ pub fn create_database(database_id: &str) -> CreateDatabaseParams { CreateDatabaseParams { database_id: database_id.to_string(), - view_id: "v1".to_string(), - view_name: "my first database".to_string(), - layout: Default::default(), - layout_settings: Default::default(), - filters: vec![], - groups: vec![], - sorts: vec![], - field_settings: field_settings_map.into(), - created_rows: vec![row_1, row_2, row_3], + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: database_id.to_string(), + view_id: "v1".to_string(), + name: "my first database view".to_string(), + field_settings: field_settings_map, + ..Default::default() + }], + rows: vec![row_1, row_2, row_3], fields: vec![field_1, field_2, field_3], } } + +pub(crate) fn expected_fields() -> Value { + json!([ + { + "field_type": 0, + "id": "f1", + "is_primary": true, + "name": "text field", + "type_options": {}, + "visibility": true, + "width": 120 + }, + { + "field_type": 2, + "id": "f2", + "is_primary": true, + "name": "single select field", + "type_options": {}, + "visibility": true, + "width": 120 + }, + { + "field_type": 1, + "id": "f3", + "is_primary": true, + "name": "checkbox field", + "type_options": {}, + "visibility": true, + "width": 120 + } + ]) +} + +pub(crate) fn expected_rows() -> Value { + json!([ + { + "cells": { + "f1": { + "data": "1f1cell" + }, + "f2": { + "data": "1f2cell" + }, + "f3": { + "data": "1f3cell" + } + }, + "created_at": 1703772730, + "last_modified": 1703772762, + "height": 0, + "id": "1", + "visibility": true + }, + { + "cells": { + "f1": { + "data": "2f1cell" + }, + "f2": { + "data": "2f2cell" + } + }, + "created_at": 1703772730, + "last_modified": 1703772762, + "height": 0, + "id": "2", + "visibility": true + }, + { + "cells": { + "f1": { + "data": "3f1cell" + }, + "f3": { + "data": "3f3cell" + } + }, + "created_at": 1703772730, + "last_modified": 1703772762, + "height": 0, + "id": "3", + "visibility": true + } + ]) +} + +pub(crate) fn expected_view() -> Value { + json!({ + "database_id": "d1", + "field_orders": [ + { + "id": "f1" + }, + { + "id": "f2" + }, + { + "id": "f3" + } + ], + "filters": [], + "created_at": 0, + "modified_at": 0, + "group_settings": [], + "id": "v1", + "layout": 0, + "layout_settings": {}, + "name": "my first database view", + "row_orders": [ + { + "height": 0, + "id": "1" + }, + { + "height": 0, + "id": "2" + }, + { + "height": 0, + "id": "3" + } + ], + "sorts": [], + "field_settings": { + "f3": { + "width": 0, + "visibility": 0 + }, + "f1": { + "visibility": 0, + "width": 0 + }, + "f2": { + "visibility": 0, + "width": 0 + } + } + }) +} diff --git a/collab-database/tests/user_test/cell_test.rs b/collab-database/tests/user_test/cell_test.rs index eba6a4f98..eff67913e 100644 --- a/collab-database/tests/user_test/cell_test.rs +++ b/collab-database/tests/user_test/cell_test.rs @@ -1,7 +1,7 @@ use collab::core::any_map::AnyMapExtension; use collab_database::rows::{new_cell_builder, CREATED_AT}; use collab_database::rows::{CreateRowParams, LAST_MODIFIED}; -use collab_database::views::CreateDatabaseParams; +use collab_database::views::{CreateDatabaseParams, CreateDatabaseViewParams}; use crate::user_test::helper::{workspace_database_test, WorkspaceDatabaseTest}; @@ -64,7 +64,12 @@ async fn update_not_exist_row_test() { let database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); @@ -80,7 +85,12 @@ async fn user_database_with_default_row() -> WorkspaceDatabaseTest { let database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); diff --git a/collab-database/tests/user_test/database_test.rs b/collab-database/tests/user_test/database_test.rs index 073b4efc1..7cede5d18 100644 --- a/collab-database/tests/user_test/database_test.rs +++ b/collab-database/tests/user_test/database_test.rs @@ -1,5 +1,6 @@ +use collab_database::database::gen_database_view_id; use collab_database::rows::CreateRowParams; -use collab_database::views::{CreateDatabaseParams, CreateViewParams}; +use collab_database::views::{CreateDatabaseParams, CreateDatabaseViewParams}; use crate::user_test::helper::{ make_default_grid, random_uid, user_database_test_with_db, user_database_test_with_default_data, @@ -9,7 +10,22 @@ use crate::user_test::helper::{ #[tokio::test] async fn create_database_test() { let uid = random_uid(); - let _ = workspace_database_test(uid).await; + let test = workspace_database_test(uid).await; + let database = test + .create_database(CreateDatabaseParams { + database_id: "d1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], + ..Default::default() + }) + .unwrap(); + + let views = database.lock().views.get_all_views(); + assert_eq!(views.len(), 1); } #[tokio::test] @@ -19,14 +35,24 @@ async fn create_multiple_database_test() { test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); test .create_database(CreateDatabaseParams { database_id: "d2".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v2".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d2".to_string(), + view_id: "v2".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); @@ -43,14 +69,24 @@ async fn delete_database_test() { test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); test .create_database(CreateDatabaseParams { database_id: "d2".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v2".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d2".to_string(), + view_id: "v2".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); @@ -67,7 +103,12 @@ async fn duplicate_database_inline_view_test() { let database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); @@ -100,13 +141,18 @@ async fn duplicate_database_view_test() { let database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); test - .create_database_linked_view(CreateViewParams { + .create_database_linked_view(CreateDatabaseViewParams { database_id: "d1".to_string(), view_id: "v2".to_string(), ..Default::default() @@ -132,13 +178,57 @@ async fn duplicate_database_view_test() { assert_eq!(database.lock().get_rows_for_view("v1").len(), 1); } +#[tokio::test] +async fn delete_database_linked_view_test() { + let test = workspace_database_test(random_uid()).await; + let database = test + .create_database(CreateDatabaseParams { + database_id: "d1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], + ..Default::default() + }) + .unwrap(); + + database + .lock() + .create_linked_view(CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v2".to_string(), + ..Default::default() + }) + .unwrap(); + + let views = database.lock().views.get_all_views(); + assert_eq!(views.len(), 2); + + database.lock().delete_view("v2"); + + let views = database.lock().views.get_all_views(); + assert_eq!(views.len(), 1); + + database.lock().delete_view("v1"); + + let views = database.lock().views.get_all_views(); + assert_eq!(views.len(), 0); +} + #[tokio::test] async fn delete_database_inline_view_test() { let test = workspace_database_test(random_uid()).await; let database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); @@ -146,7 +236,7 @@ async fn delete_database_inline_view_test() { for i in 2..5 { database .lock() - .create_linked_view(CreateViewParams { + .create_linked_view(CreateDatabaseViewParams { database_id: "d1".to_string(), view_id: format!("v{}", i), ..Default::default() @@ -154,6 +244,7 @@ async fn delete_database_inline_view_test() { .unwrap(); } + // there should be 4 views: inline-view v1 and created linked-views v2, v3 and v4. let views = database.lock().views.get_all_views(); assert_eq!(views.len(), 4); @@ -167,8 +258,7 @@ async fn delete_database_inline_view_test() { async fn duplicate_database_data_test() { let test = user_database_test_with_default_data(random_uid()).await; let original = test.get_database_with_view_id("v1").await.unwrap(); - let duplicated_data = test.get_database_duplicated_data("v1").await.unwrap(); - let duplicate = test.create_database_with_data(duplicated_data).unwrap(); + let duplicate = test.duplicate_database("v1").await.unwrap(); let duplicated_view_id = &duplicate.lock().get_all_database_views_meta()[0].id; @@ -216,13 +306,18 @@ async fn get_database_by_view_id_test() { let _database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); test - .create_database_linked_view(CreateViewParams { + .create_database_linked_view(CreateDatabaseViewParams { database_id: "d1".to_string(), view_id: "v2".to_string(), ..Default::default() @@ -238,7 +333,7 @@ async fn get_database_by_view_id_test() { async fn reopen_database_test() { let uid = random_uid(); let test = workspace_database_test(uid).await; - let view_id = uuid::Uuid::new_v4().to_string(); + let view_id = gen_database_view_id(); let params = make_default_grid(&view_id, "first view"); // create the database with inline view diff --git a/collab-database/tests/user_test/helper.rs b/collab-database/tests/user_test/helper.rs index a893165ad..ec00012be 100644 --- a/collab-database/tests/user_test/helper.rs +++ b/collab-database/tests/user_test/helper.rs @@ -15,7 +15,7 @@ use collab_database::user::{ CollabDocStateByOid, CollabFuture, DatabaseCollabService, RowRelationChange, RowRelationUpdateReceiver, WorkspaceDatabase, }; -use collab_database::views::{CreateDatabaseParams, DatabaseLayout, OrderObjectPosition}; +use collab_database::views::{CreateDatabaseParams, CreateDatabaseViewParams, DatabaseLayout}; use collab_entity::CollabType; use collab_plugins::local_storage::CollabPersistenceConfig; use parking_lot::Mutex; @@ -181,9 +181,7 @@ fn create_database_params(database_id: &str) -> CreateDatabaseParams { .insert_cell("f3", TestTextCell::from("1f3cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }; let row_2 = CreateRowParams { id: 2.into(), @@ -192,9 +190,7 @@ fn create_database_params(database_id: &str) -> CreateDatabaseParams { .insert_cell("f2", TestTextCell::from("2f2cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }; let row_3 = CreateRowParams { id: 3.into(), @@ -203,9 +199,7 @@ fn create_database_params(database_id: &str) -> CreateDatabaseParams { .insert_cell("f3", TestTextCell::from("3f3cell")) .build(), height: 0, - visibility: true, - row_position: OrderObjectPosition::default(), - timestamp: 0, + ..Default::default() }; let field_1 = Field::new("f1".to_string(), "text field".to_string(), 0, true); let field_2 = Field::new("f2".to_string(), "single select field".to_string(), 2, true); @@ -215,15 +209,15 @@ fn create_database_params(database_id: &str) -> CreateDatabaseParams { CreateDatabaseParams { database_id: database_id.to_string(), - view_id: "v1".to_string(), - view_name: "my first database".to_string(), - layout: Default::default(), - layout_settings: Default::default(), - filters: vec![], - groups: vec![], - sorts: vec![], - field_settings: field_settings_map, - created_rows: vec![row_1, row_2, row_3], + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: database_id.to_string(), + view_id: "v1".to_string(), + name: "my first database".to_string(), + field_settings: field_settings_map, + ..Default::default() + }], + rows: vec![row_1, row_2, row_3], fields: vec![field_1, field_2, field_3], } } @@ -246,6 +240,8 @@ pub async fn test_timeout(f: F) -> F::Output { } pub fn make_default_grid(view_id: &str, name: &str) -> CreateDatabaseParams { + let database_id = gen_database_id(); + let text_field = Field { id: gen_field_id(), name: "Name".to_string(), @@ -279,16 +275,17 @@ pub fn make_default_grid(view_id: &str, name: &str) -> CreateDatabaseParams { let field_settings_map = field_settings_for_default_database(); CreateDatabaseParams { - database_id: gen_database_id(), - view_id: view_id.to_string(), - view_name: name.to_string(), - layout: DatabaseLayout::Grid, - layout_settings: Default::default(), - filters: vec![], - groups: vec![], - sorts: vec![], - field_settings: field_settings_map, - created_rows: vec![ + database_id: database_id.clone(), + inline_view_id: view_id.to_string(), + views: vec![CreateDatabaseViewParams { + database_id, + view_id: view_id.to_string(), + name: name.to_string(), + layout: DatabaseLayout::Grid, + field_settings: field_settings_map, + ..Default::default() + }], + rows: vec![ CreateRowParams::new(gen_row_id()), CreateRowParams::new(gen_row_id()), CreateRowParams::new(gen_row_id()), diff --git a/collab-database/tests/user_test/type_option_test.rs b/collab-database/tests/user_test/type_option_test.rs index 29c7aca6a..d91173cc5 100644 --- a/collab-database/tests/user_test/type_option_test.rs +++ b/collab-database/tests/user_test/type_option_test.rs @@ -1,6 +1,6 @@ use collab::core::any_map::AnyMapExtension; use collab_database::fields::{Field, TypeOptionDataBuilder, TypeOptions}; -use collab_database::views::{CreateDatabaseParams, OrderObjectPosition}; +use collab_database::views::{CreateDatabaseParams, CreateDatabaseViewParams, OrderObjectPosition}; use crate::database_test::helper::default_field_settings_by_layout; use crate::user_test::helper::{workspace_database_test, WorkspaceDatabaseTest}; @@ -72,7 +72,12 @@ async fn user_database_with_default_field() -> WorkspaceDatabaseTest { let database = test .create_database(CreateDatabaseParams { database_id: "d1".to_string(), - view_id: "v1".to_string(), + inline_view_id: "v1".to_string(), + views: vec![CreateDatabaseViewParams { + database_id: "d1".to_string(), + view_id: "v1".to_string(), + ..Default::default() + }], ..Default::default() }) .unwrap(); diff --git a/collab-folder/src/view.rs b/collab-folder/src/view.rs index c8ae11704..bc7f1f245 100644 --- a/collab-folder/src/view.rs +++ b/collab-folder/src/view.rs @@ -743,6 +743,10 @@ pub enum ViewLayout { } impl ViewLayout { + pub fn is_document(&self) -> bool { + matches!(self, ViewLayout::Document) + } + pub fn is_database(&self) -> bool { matches!( self,