Browse Source

refactor: row builder

appflowy 2 năm trước cách đây
mục cha
commit
284755eccf

+ 7 - 4
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<OptionalRow, FlowyError> {
     let params: GridRowId = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
-    let row = OptionalRow {
-        row: editor.get_row(&params.row_id).await?,
-    };
-    data_result(row)
+    let row = editor
+        .get_row_rev(&params.row_id)
+        .await?
+        .and_then(make_row_from_row_rev);
+
+    data_result(OptionalRow { row })
 }
 
 #[tracing::instrument(level = "debug", skip(data, manager), err)]

+ 11 - 4
frontend/rust-lib/flowy-grid/src/services/cell/cell_operation.rs

@@ -26,8 +26,12 @@ pub trait CellDataOperation<D, C> {
     /// SelectOptionCellChangeset,DateCellChangeset. etc.  
     fn apply_changeset(&self, changeset: CellDataChangeset<C>, cell_rev: Option<CellRevision>) -> FlowyResult<String>;
 }
-/// 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<C: ToString, T: AsRef<FieldRevision>>(
     changeset: C,
     cell_rev: Option<CellRevision>,
@@ -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<Self>
     where
         Self: Sized;
 }
 
+/// CellData is a helper struct. String will be parser into Option<T> only if the T impl the FromCellString trait.
 pub struct CellData<T>(pub Option<T>);
-
 impl<T> CellData<T> {
     pub fn try_into_inner(self) -> FlowyResult<T> {
         match self.0 {
@@ -158,7 +164,8 @@ impl std::convert::From<CellData<String>> 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<Self>
     where

+ 14 - 23
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<CreateRowRevisionPayload>) -> FlowyResult<Vec<RowInfo>> {
+    pub async fn insert_rows(&self, row_revs: Vec<RowRevision>) -> FlowyResult<Vec<RowInfo>> {
         let block_id = self.block_id().await?;
         let mut rows_by_block_id: HashMap<String, Vec<RowRevision>> = 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<RepeatedRow> {
@@ -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<Option<Row>> {
+    pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<Arc<RowRevision>>> {
         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(&params.field_id, data))
     }
 
+    pub async fn get_cell_display(&self, _params: &CellIdentifier) -> Option<String> {
+        todo!()
+    }
+
     pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
         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(())
             }

+ 13 - 17
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<FieldRevision>>,
     payload: CreateRowRevisionPayload,
 }
 
-impl<'a> CreateRowRevisionBuilder<'a> {
+impl<'a> RowRevisionBuilder<'a> {
     pub fn new(fields: &'a [Arc<FieldRevision>]) -> 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,
+        }
     }
 }
 

+ 7 - 21
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<RowRevision>]) -> Ve
     row_revs.iter().map(RowInfo::from).collect::<Vec<_>>()
 }
 
-pub(crate) fn make_row_from_row_rev(fields: &[Arc<FieldRevision>], row_rev: Arc<RowRevision>) -> Option<Row> {
-    make_rows_from_row_revs(fields, &[row_rev]).pop()
+pub(crate) fn make_row_from_row_rev(row_rev: Arc<RowRevision>) -> Option<Row> {
+    make_rows_from_row_revs(&[row_rev]).pop()
 }
 
-pub(crate) fn make_rows_from_row_revs(_fields: &[Arc<FieldRevision>], row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
-    // let field_rev_map = fields
-    //     .iter()
-    //     .map(|field_rev| (&field_rev.id, field_rev))
-    //     .collect::<HashMap<&String, &FieldRevision>>();
-
-    let make_row = |row_rev: &Arc<RowRevision>| {
-        // 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::<HashMap<String, Cell>>();
-
-        Row {
-            id: row_rev.id.clone(),
-            height: row_rev.height,
-        }
+pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
+    let make_row = |row_rev: &Arc<RowRevision>| Row {
+        id: row_rev.id.clone(),
+        height: row_rev.height,
     };
 
     row_revs.iter().map(make_row).collect::<Vec<_>>()

+ 8 - 8
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()
 }

+ 20 - 89
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::<Vec<_>>()
-                    .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(&timestamp.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::<DateCellData>()
-            .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;
 }

+ 32 - 12
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<String>,
     },
+    AssertCell {
+        row_id: String,
+        field_id: String,
+        expected_display: Option<String>,
+    },
     AssertRowCount(usize),
     CreateBlock {
         block: GridBlockMetaRevision,
@@ -49,10 +55,6 @@ impl GridRowTest {
         Self { inner: editor_test }
     }
 
-    pub fn field_revs(&self) -> &Vec<Arc<FieldRevision>> {
-        &self.field_revs
-    }
-
     pub fn last_row(&self) -> Option<RowRevision> {
         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

+ 71 - 40
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<FieldRevision>],
+    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 }
+    pub fn new(block_id: &str, field_revs: &'a [Arc<FieldRevision>]) -> 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_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 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_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_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();
     }
 
-    #[allow(dead_code)]
-    pub fn update_date_cell(mut self, value: i64) -> Self {
+    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<F>(&mut self, f: F)
+    where
+        F: Fn(&Vec<SelectOption>) -> &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<F>(&mut self, f: F)
+    where
+        F: Fn(&Vec<SelectOption>) -> &Vec<SelectOption>,
+    {
+        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::<Vec<_>>()
+            .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)
     }
 }

+ 94 - 71
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()
     }
-}
-
-fn make_all_field_test_grid() -> BuildGridContext {
-    let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
-        .name("Name")
-        .visibility(true)
-        .build();
-
-    // 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();
-
-    // 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();
+    pub fn block_id(&self) -> &str {
+        &self.block_meta_revs.last().unwrap().block_id
+    }
+}
 
-    // 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 => {}
-    //         }
-    //     }
-    // }
+// 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;
+
+        // 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);
+            }
+        }
+    }
 
-    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()
 }

+ 11 - 6
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 {