From 284755eccf4f65b95d6b1a511831f0b6609d4b52 Mon Sep 17 00:00:00 2001 From: appflowy Date: Tue, 12 Jul 2022 15:49:14 +0800 Subject: [PATCH] refactor: row builder --- .../rust-lib/flowy-grid/src/event_handler.rs | 11 +- .../src/services/cell/cell_operation.rs | 15 +- .../flowy-grid/src/services/grid_editor.rs | 37 ++-- .../src/services/row/row_builder.rs | 30 ++-- .../flowy-grid/src/services/row/row_loader.rs | 28 +-- frontend/rust-lib/flowy-grid/src/util.rs | 16 +- .../tests/grid/block_test/row_test.rs | 109 +++--------- .../tests/grid/block_test/script.rs | 44 +++-- .../flowy-grid/tests/grid/block_test/util.rs | 113 +++++++----- .../flowy-grid/tests/grid/grid_editor.rs | 163 ++++++++++-------- .../src/client_grid/grid_builder.rs | 17 +- 11 files changed, 288 insertions(+), 295 deletions(-) diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs index a3b1471a4e..c2faceb9b5 100644 --- a/frontend/rust-lib/flowy-grid/src/event_handler.rs +++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs @@ -5,6 +5,7 @@ use crate::services::field::select_option::*; use crate::services::field::{ default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload, }; +use crate::services::row::make_row_from_row_rev; use flowy_error::{ErrorCode, FlowyError, FlowyResult}; use flowy_grid_data_model::revision::FieldRevision; use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams}; @@ -229,10 +230,12 @@ pub(crate) async fn get_row_handler( ) -> DataResult { let params: GridRowId = data.into_inner().try_into()?; let editor = manager.get_grid_editor(¶ms.grid_id)?; - let row = OptionalRow { - row: editor.get_row(¶ms.row_id).await?, - }; - data_result(row) + let row = editor + .get_row_rev(¶ms.row_id) + .await? + .and_then(make_row_from_row_rev); + + data_result(OptionalRow { row }) } #[tracing::instrument(level = "debug", skip(data, manager), err)] diff --git a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs index 5b74c26aa4..05c331bda8 100644 --- a/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs @@ -26,8 +26,12 @@ pub trait CellDataOperation { /// SelectOptionCellChangeset,DateCellChangeset. etc. fn apply_changeset(&self, changeset: CellDataChangeset, cell_rev: Option) -> FlowyResult; } -/// The changeset will be deserialized into specific data base on the FieldType. -/// For example, it's String on FieldType::RichText, and SelectOptionChangeset on FieldType::SingleSelect +/// changeset: It will be deserialized into specific data base on the FieldType. +/// For example, +/// FieldType::RichText => String +/// FieldType::SingleSelect => SelectOptionChangeset +/// +/// cell_rev: It will be None if the cell does not contain any data. pub fn apply_cell_data_changeset>( changeset: C, cell_rev: Option, @@ -114,14 +118,16 @@ pub fn try_decode_cell_data( } } +/// If the cell data is not String type, it should impl this trait. +/// Deserialize the String into cell specific data type. pub trait FromCellString { fn from_cell_str(s: &str) -> FlowyResult where Self: Sized; } +/// CellData is a helper struct. String will be parser into Option only if the T impl the FromCellString trait. pub struct CellData(pub Option); - impl CellData { pub fn try_into_inner(self) -> FlowyResult { match self.0 { @@ -158,7 +164,8 @@ impl std::convert::From> for String { } } -// CellChangeset +/// If the changeset applying to the cell is not String type, it should impl this trait. +/// Deserialize the string into cell specific changeset. pub trait FromCellChangeset { fn from_changeset(changeset: String) -> FlowyResult where diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs index 1d63b6dd69..93d9317565 100644 --- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs @@ -8,8 +8,7 @@ use crate::services::field::{default_type_option_builder_from_type, type_option_ use crate::services::filter::{GridFilterChangeset, GridFilterService}; use crate::services::persistence::block_index::BlockIndexCache; use crate::services::row::{ - make_grid_blocks, make_row_from_row_rev, make_row_rev_from_context, make_rows_from_row_revs, - CreateRowRevisionBuilder, CreateRowRevisionPayload, GridBlockSnapshot, + make_grid_blocks, make_row_from_row_rev, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder, }; use crate::services::setting::make_grid_setting; use bytes::Bytes; @@ -274,8 +273,7 @@ impl GridRevisionEditor { let block_id = self.block_id().await?; // insert empty row below the row whose id is upper_row_id - let row_rev_ctx = CreateRowRevisionBuilder::new(&field_revs).build(); - let row_rev = make_row_rev_from_context(&block_id, row_rev_ctx); + let row_rev = RowRevisionBuilder::new(&field_revs).build(&block_id); let row_order = RowInfo::from(&row_rev); // insert the row @@ -287,12 +285,11 @@ impl GridRevisionEditor { Ok(row_order) } - pub async fn insert_rows(&self, contexts: Vec) -> FlowyResult> { + pub async fn insert_rows(&self, row_revs: Vec) -> FlowyResult> { let block_id = self.block_id().await?; let mut rows_by_block_id: HashMap> = HashMap::new(); let mut row_orders = vec![]; - for ctx in contexts { - let row_rev = make_row_rev_from_context(&block_id, ctx); + for row_rev in row_revs { row_orders.push(RowInfo::from(&row_rev)); rows_by_block_id .entry(block_id.clone()) @@ -307,10 +304,7 @@ impl GridRevisionEditor { } pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> { - let field_revs = self.get_field_revs(None).await?; - self.block_manager - .update_row(changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev)) - .await + self.block_manager.update_row(changeset, make_row_from_row_rev).await } pub async fn get_rows(&self, block_id: &str) -> FlowyResult { @@ -322,26 +316,20 @@ impl GridRevisionEditor { debug_assert_eq!(grid_block_snapshot.len(), 1); if grid_block_snapshot.len() == 1 { let snapshot = grid_block_snapshot.pop().unwrap(); - let field_revs = self.get_field_revs(None).await?; - let rows = make_rows_from_row_revs(&field_revs, &snapshot.row_revs); + let rows = make_rows_from_row_revs(&snapshot.row_revs); Ok(rows.into()) } else { Ok(vec![].into()) } } - pub async fn get_row(&self, row_id: &str) -> FlowyResult> { + pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult>> { match self.block_manager.get_row_rev(row_id).await? { None => Ok(None), - Some(row_rev) => { - let field_revs = self.get_field_revs(None).await?; - let row_revs = vec![row_rev]; - let mut rows = make_rows_from_row_revs(&field_revs, &row_revs); - debug_assert!(rows.len() == 1); - Ok(rows.pop()) - } + Some(row_rev) => Ok(Some(row_rev)), } } + pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> { let _ = self.block_manager.delete_row(row_id).await?; Ok(()) @@ -360,6 +348,10 @@ impl GridRevisionEditor { Some(Cell::new(¶ms.field_id, data)) } + pub async fn get_cell_display(&self, _params: &CellIdentifier) -> Option { + todo!() + } + pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult> { let row_rev = self.block_manager.get_row_rev(row_id).await?; match row_rev { @@ -395,7 +387,6 @@ impl GridRevisionEditor { let cell_rev = self.get_cell_rev(&row_id, &field_id).await?; // Update the changeset.data property with the return value. content = Some(apply_cell_data_changeset(content.unwrap(), cell_rev, field_rev)?); - let field_revs = self.get_field_revs(None).await?; let cell_changeset = CellChangeset { grid_id, row_id, @@ -404,7 +395,7 @@ impl GridRevisionEditor { }; let _ = self .block_manager - .update_cell(cell_changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev)) + .update_cell(cell_changeset, make_row_from_row_rev) .await?; Ok(()) } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs index 3302276cd8..cc86fa3d9d 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_builder.rs @@ -6,12 +6,12 @@ use indexmap::IndexMap; use std::collections::HashMap; use std::sync::Arc; -pub struct CreateRowRevisionBuilder<'a> { +pub struct RowRevisionBuilder<'a> { field_rev_map: HashMap<&'a String, &'a Arc>, payload: CreateRowRevisionPayload, } -impl<'a> CreateRowRevisionBuilder<'a> { +impl<'a> RowRevisionBuilder<'a> { pub fn new(fields: &'a [Arc]) -> Self { let field_rev_map = fields .iter() @@ -28,10 +28,10 @@ impl<'a> CreateRowRevisionBuilder<'a> { Self { field_rev_map, payload } } - pub fn add_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { + pub fn insert_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { match self.field_rev_map.get(&field_id.to_owned()) { None => { - let msg = format!("Invalid field_id: {}", field_id); + let msg = format!("Can't find the field with id: {}", field_id); Err(FlowyError::internal().context(msg)) } Some(field_rev) => { @@ -43,7 +43,7 @@ impl<'a> CreateRowRevisionBuilder<'a> { } } - pub fn add_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { + pub fn insert_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> { match self.field_rev_map.get(&field_id.to_owned()) { None => { let msg = format!("Invalid field_id: {}", field_id); @@ -71,18 +71,14 @@ impl<'a> CreateRowRevisionBuilder<'a> { self } - pub fn build(self) -> CreateRowRevisionPayload { - self.payload - } -} - -pub fn make_row_rev_from_context(block_id: &str, payload: CreateRowRevisionPayload) -> RowRevision { - RowRevision { - id: payload.row_id, - block_id: block_id.to_owned(), - cells: payload.cell_by_field_id, - height: payload.height, - visibility: payload.visibility, + pub fn build(self, block_id: &str) -> RowRevision { + RowRevision { + id: self.payload.row_id, + block_id: block_id.to_owned(), + cells: self.payload.cell_by_field_id, + height: self.payload.height, + visibility: self.payload.visibility, + } } } diff --git a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs index 1b9ce80101..81d9420d88 100644 --- a/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs +++ b/frontend/rust-lib/flowy-grid/src/services/row/row_loader.rs @@ -1,6 +1,6 @@ use crate::entities::{GridBlock, RepeatedGridBlock, Row, RowInfo}; use flowy_error::FlowyResult; -use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use flowy_grid_data_model::revision::RowRevision; use std::collections::HashMap; use std::sync::Arc; @@ -39,28 +39,14 @@ pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc]) -> Ve row_revs.iter().map(RowInfo::from).collect::>() } -pub(crate) fn make_row_from_row_rev(fields: &[Arc], row_rev: Arc) -> Option { - make_rows_from_row_revs(fields, &[row_rev]).pop() +pub(crate) fn make_row_from_row_rev(row_rev: Arc) -> Option { + make_rows_from_row_revs(&[row_rev]).pop() } -pub(crate) fn make_rows_from_row_revs(_fields: &[Arc], row_revs: &[Arc]) -> Vec { - // let field_rev_map = fields - // .iter() - // .map(|field_rev| (&field_rev.id, field_rev)) - // .collect::>(); - - let make_row = |row_rev: &Arc| { - // let cell_by_field_id = row_rev - // .cells - // .clone() - // .into_iter() - // .flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev)) - // .collect::>(); - - Row { - id: row_rev.id.clone(), - height: row_rev.height, - } +pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc]) -> Vec { + let make_row = |row_rev: &Arc| Row { + id: row_rev.id.clone(), + height: row_rev.height, }; row_revs.iter().map(make_row).collect::>() diff --git a/frontend/rust-lib/flowy-grid/src/util.rs b/frontend/rust-lib/flowy-grid/src/util.rs index e0055d09b6..3b48e313a9 100644 --- a/frontend/rust-lib/flowy-grid/src/util.rs +++ b/frontend/rust-lib/flowy-grid/src/util.rs @@ -4,29 +4,29 @@ use flowy_grid_data_model::revision::BuildGridContext; use flowy_sync::client_grid::GridBuilder; pub fn make_default_grid() -> BuildGridContext { + let mut grid_builder = GridBuilder::new(); // text let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) .name("Name") .visibility(true) .primary(true) .build(); + grid_builder.add_field(text_field); // single select let single_select = SingleSelectTypeOptionBuilder::default(); let single_select_field = FieldBuilder::new(single_select).name("Type").visibility(true).build(); + grid_builder.add_field(single_select_field); // checkbox let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox) .name("Done") .visibility(true) .build(); + grid_builder.add_field(checkbox_field); - GridBuilder::default() - .add_field(text_field) - .add_field(single_select_field) - .add_field(checkbox_field) - .add_empty_row() - .add_empty_row() - .add_empty_row() - .build() + grid_builder.add_empty_row(); + grid_builder.add_empty_row(); + grid_builder.add_empty_row(); + grid_builder.build() } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs index 96c23611b0..ec57893d9b 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/row_test.rs @@ -1,13 +1,5 @@ use crate::grid::block_test::script::GridRowTest; use crate::grid::block_test::script::RowScript::*; -use crate::grid::block_test::util::GridRowTestBuilder; -use chrono::NaiveDateTime; -use flowy_grid::entities::FieldType; -use flowy_grid::services::cell::decode_any_cell_data; -use flowy_grid::services::field::select_option::SELECTION_IDS_SEPARATOR; -use flowy_grid::services::field::{DateCellData, MultiSelectTypeOption, SingleSelectTypeOption}; - -use crate::grid::field_test::util::make_date_cell_string; use flowy_grid_data_model::revision::RowMetaChangeset; #[tokio::test] @@ -18,7 +10,7 @@ async fn grid_create_row_count_test() { CreateEmptyRow, CreateEmptyRow, CreateRow { - payload: GridRowTestBuilder::new(&test).build(), + row_rev: test.row_builder().build(), }, AssertRowCount(6), ]; @@ -28,15 +20,15 @@ async fn grid_create_row_count_test() { #[tokio::test] async fn grid_update_row() { let mut test = GridRowTest::new().await; - let payload = GridRowTestBuilder::new(&test).build(); + let row_rev = test.row_builder().build(); let changeset = RowMetaChangeset { - row_id: payload.row_id.clone(), + row_id: row_rev.id.clone(), height: None, visibility: None, cell_by_field_id: Default::default(), }; - let scripts = vec![AssertRowCount(3), CreateRow { payload }, UpdateRow { changeset }]; + let scripts = vec![AssertRowCount(3), CreateRow { row_rev }, UpdateRow { changeset }]; test.run_scripts(scripts).await; let expected_row = test.last_row().unwrap(); @@ -47,13 +39,13 @@ async fn grid_update_row() { #[tokio::test] async fn grid_delete_row() { let mut test = GridRowTest::new().await; - let payload1 = GridRowTestBuilder::new(&test).build(); - let payload2 = GridRowTestBuilder::new(&test).build(); - let row_ids = vec![payload1.row_id.clone(), payload2.row_id.clone()]; + let row_1 = test.row_builder().build(); + let row_2 = test.row_builder().build(); + let row_ids = vec![row_1.id.clone(), row_2.id.clone()]; let scripts = vec![ AssertRowCount(3), - CreateRow { payload: payload1 }, - CreateRow { payload: payload2 }, + CreateRow { row_rev: row_1 }, + CreateRow { row_rev: row_2 }, AssertBlockCount(1), AssertBlock { block_index: 0, @@ -73,78 +65,17 @@ async fn grid_delete_row() { #[tokio::test] async fn grid_row_add_cells_test() { let mut test = GridRowTest::new().await; - let mut builder = test.builder(); - for field in test.field_revs() { - let field_type: FieldType = field.field_type_rev.into(); - match field_type { - FieldType::RichText => { - builder.add_cell(&field.id, "hello world".to_owned()).unwrap(); - } - FieldType::Number => { - builder.add_cell(&field.id, "18,443".to_owned()).unwrap(); - } - FieldType::DateTime => { - builder - .add_cell(&field.id, make_date_cell_string("1647251762")) - .unwrap(); - } - FieldType::SingleSelect => { - let type_option = SingleSelectTypeOption::from(field); - let option = type_option.options.first().unwrap(); - builder.add_select_option_cell(&field.id, option.id.clone()).unwrap(); - } - FieldType::MultiSelect => { - let type_option = MultiSelectTypeOption::from(field); - let ops_ids = type_option - .options - .iter() - .map(|option| option.id.clone()) - .collect::>() - .join(SELECTION_IDS_SEPARATOR); - builder.add_select_option_cell(&field.id, ops_ids).unwrap(); - } - FieldType::Checkbox => { - builder.add_cell(&field.id, "false".to_string()).unwrap(); - } - FieldType::URL => { - builder.add_cell(&field.id, "1".to_string()).unwrap(); - } - } - } - let context = builder.build(); - let scripts = vec![CreateRow { payload: context }]; - test.run_scripts(scripts).await; -} + let mut builder = test.row_builder(); -#[tokio::test] -async fn grid_row_add_date_cell_test() { - let mut test = GridRowTest::new().await; - let mut builder = test.builder(); - let mut date_field = None; - let timestamp = 1647390674; - for field in test.field_revs() { - let field_type: FieldType = field.field_type_rev.into(); - if field_type == FieldType::DateTime { - date_field = Some(field.clone()); - NaiveDateTime::from_timestamp(123, 0); - // The data should not be empty - assert!(builder.add_cell(&field.id, "".to_string()).is_err()); - assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok()); - assert!(builder - .add_cell(&field.id, make_date_cell_string(×tamp.to_string())) - .is_ok()); - } - } - let context = builder.build(); - let date_field = date_field.unwrap(); - let cell_rev = context.cell_by_field_id.get(&date_field.id).unwrap(); - assert_eq!( - decode_any_cell_data(cell_rev, &date_field) - .parse::() - .unwrap() - .date, - "2022/03/16", - ); - let scripts = vec![CreateRow { payload: context }]; + builder.insert_text_cell("hello world"); + builder.insert_number_cell("18,443"); + builder.insert_date_cell("1647251762"); + builder.insert_single_select_cell(|options| options.first().unwrap()); + builder.insert_multi_select_cell(|options| options); + builder.insert_checkbox_cell("false"); + builder.insert_url_cell("1"); + + let row_rev = builder.build(); + let scripts = vec![CreateRow { row_rev }]; test.run_scripts(scripts).await; } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs index 288133958b..642cd51670 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/script.rs @@ -1,15 +1,16 @@ +use crate::grid::block_test::util::GridRowTestBuilder; use crate::grid::grid_editor::GridEditorTest; -use flowy_grid::entities::RowInfo; -use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload}; +use flowy_grid::entities::{CellIdentifier, RowInfo}; + use flowy_grid_data_model::revision::{ - FieldRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision, + GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision, }; use std::sync::Arc; pub enum RowScript { CreateEmptyRow, CreateRow { - payload: CreateRowRevisionPayload, + row_rev: RowRevision, }, UpdateRow { changeset: RowMetaChangeset, @@ -20,6 +21,11 @@ pub enum RowScript { DeleteRows { row_ids: Vec, }, + AssertCell { + row_id: String, + field_id: String, + expected_display: Option, + }, AssertRowCount(usize), CreateBlock { block: GridBlockMetaRevision, @@ -49,10 +55,6 @@ impl GridRowTest { Self { inner: editor_test } } - pub fn field_revs(&self) -> &Vec> { - &self.field_revs - } - pub fn last_row(&self) -> Option { self.row_revs.last().map(|a| a.clone().as_ref().clone()) } @@ -63,8 +65,8 @@ impl GridRowTest { } } - pub fn builder(&self) -> CreateRowRevisionBuilder { - CreateRowRevisionBuilder::new(&self.field_revs) + pub fn row_builder(&self) -> GridRowTestBuilder { + GridRowTestBuilder::new(self.block_id(), &self.field_revs) } pub async fn run_script(&mut self, script: RowScript) { @@ -76,8 +78,8 @@ impl GridRowTest { self.row_revs = self.get_row_revs().await; self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); } - RowScript::CreateRow { payload: context } => { - let row_orders = self.editor.insert_rows(vec![context]).await.unwrap(); + RowScript::CreateRow { row_rev } => { + let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap(); for row_order in row_orders { self.row_order_by_row_id .insert(row_order.row_id().to_owned(), row_order); @@ -96,6 +98,24 @@ impl GridRowTest { self.row_revs = self.get_row_revs().await; self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap(); } + RowScript::AssertCell { + row_id, + field_id, + expected_display, + } => { + let id = CellIdentifier { + grid_id: self.grid_id.clone(), + field_id, + row_id, + }; + let display = self.editor.get_cell_display(&id).await; + match expected_display { + None => {} + Some(expected_display) => { + assert_eq!(display.unwrap(), expected_display); + } + } + } RowScript::AssertRow { expected_row } => { let row = &*self .row_revs diff --git a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs index 49b8383fee..baf1bf6ae0 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/block_test/util.rs @@ -1,66 +1,97 @@ -use crate::grid::block_test::script::GridRowTest; - use flowy_grid::entities::FieldType; -use flowy_grid::services::field::DateCellChangeset; -use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload}; -use flowy_grid_data_model::revision::FieldRevision; +use flowy_grid::services::field::select_option::{SelectOption, SELECTION_IDS_SEPARATOR}; +use flowy_grid::services::field::{DateCellChangeset, MultiSelectTypeOption, SingleSelectTypeOption}; +use flowy_grid::services::row::RowRevisionBuilder; +use flowy_grid_data_model::revision::{FieldRevision, RowRevision}; +use std::sync::Arc; use strum::EnumCount; pub struct GridRowTestBuilder<'a> { - test: &'a GridRowTest, - inner_builder: CreateRowRevisionBuilder<'a>, + block_id: String, + field_revs: &'a [Arc], + inner_builder: RowRevisionBuilder<'a>, } impl<'a> GridRowTestBuilder<'a> { - pub fn new(test: &'a GridRowTest) -> Self { - assert_eq!(test.field_revs().len(), FieldType::COUNT); - - let inner_builder = CreateRowRevisionBuilder::new(test.field_revs()); - Self { test, inner_builder } - } - #[allow(dead_code)] - pub fn update_text_cell(mut self, data: String) -> Self { - let text_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.add_cell(&text_field.id, data).unwrap(); - self + pub fn new(block_id: &str, field_revs: &'a [Arc]) -> Self { + assert_eq!(field_revs.len(), FieldType::COUNT); + let inner_builder = RowRevisionBuilder::new(field_revs); + Self { + block_id: block_id.to_owned(), + field_revs, + inner_builder, + } } - #[allow(dead_code)] - pub fn update_number_cell(mut self, data: String) -> Self { - let number_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.add_cell(&number_field.id, data).unwrap(); - self + pub fn insert_text_cell(&mut self, data: &str) { + let text_field = self.field_rev_with_type(&FieldType::RichText); + self.inner_builder + .insert_cell(&text_field.id, data.to_string()) + .unwrap(); } - #[allow(dead_code)] - pub fn update_date_cell(mut self, value: i64) -> Self { + pub fn insert_number_cell(&mut self, data: &str) { + let number_field = self.field_rev_with_type(&FieldType::Number); + self.inner_builder + .insert_cell(&number_field.id, data.to_string()) + .unwrap(); + } + + pub fn insert_date_cell(&mut self, data: &str) { let value = serde_json::to_string(&DateCellChangeset { - date: Some(value.to_string()), + date: Some(data.to_string()), time: None, }) .unwrap(); let date_field = self.field_rev_with_type(&FieldType::DateTime); - self.inner_builder.add_cell(&date_field.id, value).unwrap(); - self + self.inner_builder.insert_cell(&date_field.id, value).unwrap(); } - #[allow(dead_code)] - pub fn update_checkbox_cell(mut self, data: bool) -> Self { + pub fn insert_checkbox_cell(&mut self, data: &str) { let number_field = self.field_rev_with_type(&FieldType::Checkbox); - self.inner_builder.add_cell(&number_field.id, data.to_string()).unwrap(); - self + self.inner_builder + .insert_cell(&number_field.id, data.to_string()) + .unwrap(); } - #[allow(dead_code)] - pub fn update_url_cell(mut self, data: String) -> Self { - let number_field = self.field_rev_with_type(&FieldType::Checkbox); - self.inner_builder.add_cell(&number_field.id, data).unwrap(); - self + pub fn insert_url_cell(&mut self, data: &str) { + let number_field = self.field_rev_with_type(&FieldType::URL); + self.inner_builder + .insert_cell(&number_field.id, data.to_string()) + .unwrap(); + } + + pub fn insert_single_select_cell(&mut self, f: F) + where + F: Fn(&Vec) -> &SelectOption, + { + let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect); + let type_option = SingleSelectTypeOption::from(&single_select_field); + let option = f(&type_option.options); + self.inner_builder + .insert_select_option_cell(&single_select_field.id, option.id.clone()) + .unwrap(); + } + + pub fn insert_multi_select_cell(&mut self, f: F) + where + F: Fn(&Vec) -> &Vec, + { + let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect); + let type_option = MultiSelectTypeOption::from(&multi_select_field); + let options = f(&type_option.options); + let ops_ids = options + .iter() + .map(|option| option.id.clone()) + .collect::>() + .join(SELECTION_IDS_SEPARATOR); + self.inner_builder + .insert_select_option_cell(&multi_select_field.id, ops_ids) + .unwrap(); } pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision { - self.test - .field_revs() + self.field_revs .iter() .find(|field_rev| { let t_field_type: FieldType = field_rev.field_type_rev.into(); @@ -71,7 +102,7 @@ impl<'a> GridRowTestBuilder<'a> { .clone() } - pub fn build(self) -> CreateRowRevisionPayload { - self.inner_builder.build() + pub fn build(self) -> RowRevision { + self.inner_builder.build(&self.block_id) } } diff --git a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs index d944aac6c5..85c81c4058 100644 --- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs +++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs @@ -6,7 +6,7 @@ use flowy_grid::entities::*; use flowy_grid::services::field::select_option::SelectOption; use flowy_grid::services::field::*; use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor}; -use flowy_grid::services::row::CreateRowRevisionPayload; +use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder}; use flowy_grid::services::setting::GridSettingChangesetBuilder; use flowy_grid_data_model::revision::*; use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS; @@ -20,6 +20,7 @@ use std::collections::HashMap; use std::sync::Arc; use std::time::Duration; use strum::EnumCount; +use strum::IntoEnumIterator; use tokio::time::sleep; pub struct GridEditorTest { @@ -37,14 +38,13 @@ impl GridEditorTest { pub async fn new() -> Self { let sdk = FlowySDKTest::default(); let _ = sdk.init_user().await; - let build_context = make_all_field_test_grid(); + let build_context = make_test_grid(); let view_data: Bytes = build_context.into(); let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await; let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap(); let field_revs = editor.get_field_revs(None).await.unwrap(); let block_meta_revs = editor.get_block_meta_revs().await.unwrap(); let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs; - assert_eq!(row_revs.len(), 3); assert_eq!(block_meta_revs.len(), 1); // It seems like you should add the field in the make_test_grid() function. @@ -90,75 +90,98 @@ impl GridEditorTest { .pop() .unwrap() } + + pub fn block_id(&self) -> &str { + &self.block_meta_revs.last().unwrap().block_id + } } -fn make_all_field_test_grid() -> BuildGridContext { - let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) - .name("Name") - .visibility(true) - .build(); +// This grid is assumed to contain all the Fields. +fn make_test_grid() -> BuildGridContext { + let mut grid_builder = GridBuilder::new(); + // Iterate through the FieldType to create the corresponding Field. + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; - // Single Select - let single_select = SingleSelectTypeOptionBuilder::default() - .option(SelectOption::new("Live")) - .option(SelectOption::new("Completed")) - .option(SelectOption::new("Planned")) - .option(SelectOption::new("Paused")); - let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build(); + // The + match field_type { + FieldType::RichText => { + let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default()) + .name("Name") + .visibility(true) + .build(); + grid_builder.add_field(text_field); + } + FieldType::Number => { + // Number + let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); + let number_field = FieldBuilder::new(number).name("Price").visibility(true).build(); + grid_builder.add_field(number_field); + } + FieldType::DateTime => { + // Date + let date = DateTypeOptionBuilder::default() + .date_format(DateFormat::US) + .time_format(TimeFormat::TwentyFourHour); + let date_field = FieldBuilder::new(date).name("Time").visibility(true).build(); + grid_builder.add_field(date_field); + } + FieldType::SingleSelect => { + // Single Select + let single_select = SingleSelectTypeOptionBuilder::default() + .option(SelectOption::new("Live")) + .option(SelectOption::new("Completed")) + .option(SelectOption::new("Planned")) + .option(SelectOption::new("Paused")); + let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build(); + grid_builder.add_field(single_select_field); + } + FieldType::MultiSelect => { + // MultiSelect + let multi_select = MultiSelectTypeOptionBuilder::default() + .option(SelectOption::new("Google")) + .option(SelectOption::new("Facebook")) + .option(SelectOption::new("Twitter")); + let multi_select_field = FieldBuilder::new(multi_select) + .name("Platform") + .visibility(true) + .build(); + grid_builder.add_field(multi_select_field); + } + FieldType::Checkbox => { + // Checkbox + let checkbox = CheckboxTypeOptionBuilder::default(); + let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build(); + grid_builder.add_field(checkbox_field); + } + FieldType::URL => { + // URL + let url = URLTypeOptionBuilder::default(); + let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); + grid_builder.add_field(url_field); + } + } + } - // MultiSelect - let multi_select = MultiSelectTypeOptionBuilder::default() - .option(SelectOption::new("Google")) - .option(SelectOption::new("Facebook")) - .option(SelectOption::new("Twitter")); - let multi_select_field = FieldBuilder::new(multi_select) - .name("Platform") - .visibility(true) - .build(); - - // Number - let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD); - let number_field = FieldBuilder::new(number).name("Price").visibility(true).build(); - - // Date - let date = DateTypeOptionBuilder::default() - .date_format(DateFormat::US) - .time_format(TimeFormat::TwentyFourHour); - let date_field = FieldBuilder::new(date).name("Time").visibility(true).build(); - - // Checkbox - let checkbox = CheckboxTypeOptionBuilder::default(); - let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build(); - - // URL - let url = URLTypeOptionBuilder::default(); - let url_field = FieldBuilder::new(url).name("link").visibility(true).build(); - - // for i in 0..3 { - // for field_type in FieldType::iter() { - // let field_type: FieldType = field_type; - // match field_type { - // FieldType::RichText => {} - // FieldType::Number => {} - // FieldType::DateTime => {} - // FieldType::SingleSelect => {} - // FieldType::MultiSelect => {} - // FieldType::Checkbox => {} - // FieldType::URL => {} - // } - // } - // } - - GridBuilder::default() - .add_field(text_field) - .add_field(single_select_field) - .add_field(multi_select_field) - .add_field(number_field) - .add_field(date_field) - .add_field(checkbox_field) - .add_field(url_field) - .add_empty_row() - .add_empty_row() - .add_empty_row() - .build() + // We have many assumptions base on the number of the rows, so do not change the number of the loop. + for _i in 0..10 { + for field_type in FieldType::iter() { + let field_type: FieldType = field_type; + // let mut row_builder = RowRevisionBuilder::new() + match field_type { + FieldType::RichText => {} + FieldType::Number => {} + FieldType::DateTime => {} + FieldType::SingleSelect => {} + FieldType::MultiSelect => {} + FieldType::Checkbox => {} + FieldType::URL => {} + } + } + } + // assert_eq!(row_revs.len(), 10); + // .add_empty_row() + // .add_empty_row() + // .add_empty_row() + grid_builder.build() } diff --git a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs index d1fbed3f53..1dffa6c0c6 100644 --- a/shared-lib/flowy-sync/src/client_grid/grid_builder.rs +++ b/shared-lib/flowy-sync/src/client_grid/grid_builder.rs @@ -26,18 +26,23 @@ impl std::default::Default for GridBuilder { } impl GridBuilder { - pub fn add_field(mut self, field: FieldRevision) -> Self { + pub fn new() -> Self { + Self::default() + } + pub fn add_field(&mut self, field: FieldRevision) { self.build_context.field_revs.push(field); - self } - pub fn add_empty_row(mut self) -> Self { - let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id); + pub fn add_row(&mut self, row_rev: RowRevision) { let block_meta_rev = self.build_context.blocks.first_mut().unwrap(); let block_rev = self.build_context.blocks_meta_data.first_mut().unwrap(); - block_rev.rows.push(Arc::new(row)); + block_rev.rows.push(Arc::new(row_rev)); block_meta_rev.row_count += 1; - self + } + + pub fn add_empty_row(&mut self) { + let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id); + self.add_row(row); } pub fn build(self) -> BuildGridContext {