From 659ae5d523074f5d96d7c83ca75f5fc4c43dcb25 Mon Sep 17 00:00:00 2001
From: appflowy <annie@appflowy.io>
Date: Fri, 19 Aug 2022 11:56:47 +0800
Subject: [PATCH] chore: fix some bugs and add more tests

---
 .../flowy-grid/src/entities/cell_entities.rs  |   6 +-
 .../flowy-grid/src/entities/field_entities.rs |   2 +-
 .../src/entities/filter_entities/util.rs      |   2 +-
 .../src/entities/group_entities/group.rs      |   7 +-
 .../rust-lib/flowy-grid/src/event_handler.rs  |  24 ++--
 .../src/services/cell/cell_operation.rs       |   4 +-
 .../src/services/field/field_builder.rs       |   2 +-
 .../date_type_option_entities.rs              |   4 +-
 .../multi_select_type_option.rs               |   2 +-
 .../selection_type_option/select_option.rs    |   6 +-
 .../single_select_type_option.rs              |   2 +-
 .../src/services/filter/filter_cache.rs       |   4 +-
 .../src/services/filter/filter_service.rs     |   2 +-
 .../flowy-grid/src/services/grid_editor.rs    |  14 +--
 .../src/services/grid_view_editor.rs          |   2 +-
 .../src/services/grid_view_manager.rs         |   4 +
 .../group/group_generator/group_controller.rs |   3 +-
 .../group_generator/select_option_group.rs    |  19 ++--
 .../src/services/group/group_service.rs       |   8 +-
 .../flowy-grid/tests/grid/block_test/util.rs  |   2 +-
 .../flowy-grid/tests/grid/cell_test/test.rs   |   4 +-
 .../flowy-grid/tests/grid/field_test/util.rs  |   8 +-
 .../tests/grid/filter_test/script.rs          |   2 +-
 .../flowy-grid/tests/grid/grid_editor.rs      |   2 +-
 .../tests/grid/group_test/script.rs           |  63 ++++++++++-
 .../flowy-grid/tests/grid/group_test/test.rs  | 106 +++++++++++++++++-
 .../src/revision/grid_rev.rs                  |   6 +-
 .../src/revision/grid_setting_rev.rs          |   2 +-
 .../src/client_grid/grid_revision_pad.rs      |   6 +-
 29 files changed, 243 insertions(+), 75 deletions(-)

diff --git a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs
index f7aa30649c..3493f0940c 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/cell_entities.rs
@@ -131,8 +131,8 @@ pub struct CellChangesetPB {
     #[pb(index = 3)]
     pub field_id: String,
 
-    #[pb(index = 4, one_of)]
-    pub content: Option<String>,
+    #[pb(index = 4)]
+    pub content: String,
 }
 
 impl std::convert::From<CellChangesetPB> for RowChangeset {
@@ -140,7 +140,7 @@ impl std::convert::From<CellChangesetPB> for RowChangeset {
         let mut cell_by_field_id = HashMap::with_capacity(1);
         let field_id = changeset.field_id;
         let cell_rev = CellRevision {
-            data: changeset.content.unwrap_or_else(|| "".to_owned()),
+            data: changeset.content,
         };
         cell_by_field_id.insert(field_id, cell_rev);
 
diff --git a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
index 3dc296f34e..8875b3fc26 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/field_entities.rs
@@ -42,7 +42,7 @@ impl std::convert::From<FieldRevision> for FieldPB {
             id: field_rev.id,
             name: field_rev.name,
             desc: field_rev.desc,
-            field_type: field_rev.field_type_rev.into(),
+            field_type: field_rev.ty.into(),
             frozen: field_rev.frozen,
             visibility: field_rev.visibility,
             width: field_rev.width,
diff --git a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
index 687b371345..079b6fd6dc 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
@@ -92,7 +92,7 @@ impl CreateGridFilterPayloadPB {
     pub fn new<T: Into<i32>>(field_rev: &FieldRevision, condition: T, content: Option<String>) -> Self {
         Self {
             field_id: field_rev.id.clone(),
-            field_type: field_rev.field_type_rev.into(),
+            field_type: field_rev.ty.into(),
             condition: condition.into(),
             content,
         }
diff --git a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
index fc32cb79ba..32f7c4543a 100644
--- a/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
+++ b/frontend/rust-lib/flowy-grid/src/entities/group_entities/group.rs
@@ -47,12 +47,15 @@ impl std::ops::DerefMut for RepeatedGridGroupPB {
 #[derive(ProtoBuf, Debug, Default, Clone)]
 pub struct GroupPB {
     #[pb(index = 1)]
-    pub group_id: String,
+    pub field_id: String,
 
     #[pb(index = 2)]
-    pub desc: String,
+    pub group_id: String,
 
     #[pb(index = 3)]
+    pub desc: String,
+
+    #[pb(index = 4)]
     pub rows: Vec<RowPB>,
 }
 
diff --git a/frontend/rust-lib/flowy-grid/src/event_handler.rs b/frontend/rust-lib/flowy-grid/src/event_handler.rs
index b9484ad40d..3108acd977 100644
--- a/frontend/rust-lib/flowy-grid/src/event_handler.rs
+++ b/frontend/rust-lib/flowy-grid/src/event_handler.rs
@@ -171,7 +171,7 @@ pub(crate) async fn get_field_type_option_data_handler(
     match editor.get_field_rev(&params.field_id).await {
         None => Err(FlowyError::record_not_found()),
         Some(field_rev) => {
-            let field_type = field_rev.field_type_rev.into();
+            let field_type = field_rev.ty.into();
             let type_option_data = get_type_option_data(&field_rev, &field_type).await?;
             let data = FieldTypeOptionDataPB {
                 grid_id: params.grid_id,
@@ -192,7 +192,7 @@ pub(crate) async fn create_field_type_option_data_handler(
     let params: CreateFieldParams = data.into_inner().try_into()?;
     let editor = manager.get_grid_editor(&params.grid_id)?;
     let field_rev = editor.create_next_field_rev(&params.field_type).await?;
-    let field_type: FieldType = field_rev.field_type_rev.into();
+    let field_type: FieldType = field_rev.ty.into();
     let type_option_data = get_type_option_data(&field_rev, &field_type).await?;
 
     data_result(FieldTypeOptionDataPB {
@@ -218,7 +218,7 @@ async fn get_type_option_data(field_rev: &FieldRevision, field_type: &FieldType)
     let s = field_rev
         .get_type_option_str(field_type)
         .unwrap_or_else(|| default_type_option_builder_from_type(field_type).entry().json_str());
-    let field_type: FieldType = field_rev.field_type_rev.into();
+    let field_type: FieldType = field_rev.ty.into();
     let builder = type_option_builder_from_json_str(&s, &field_type);
     let type_option_data = builder.entry().protobuf_bytes().to_vec();
 
@@ -352,13 +352,15 @@ pub(crate) async fn update_select_option_handler(
         mut_field_rev.insert_type_option_entry(&*type_option);
         let _ = editor.replace_field(field_rev).await?;
 
-        let changeset = CellChangesetPB {
-            grid_id: changeset.cell_identifier.grid_id,
-            row_id: changeset.cell_identifier.row_id,
-            field_id: changeset.cell_identifier.field_id,
-            content: cell_content_changeset,
-        };
-        let _ = editor.update_cell(changeset).await?;
+        if let Some(cell_content_changeset) = cell_content_changeset {
+            let changeset = CellChangesetPB {
+                grid_id: changeset.cell_identifier.grid_id,
+                row_id: changeset.cell_identifier.row_id,
+                field_id: changeset.cell_identifier.field_id,
+                content: cell_content_changeset,
+            };
+            let _ = editor.update_cell(changeset).await?;
+        }
     }
     Ok(())
 }
@@ -382,7 +384,7 @@ pub(crate) async fn get_select_option_handler(
             let any_cell_data: AnyCellData = match cell_rev {
                 None => AnyCellData {
                     data: "".to_string(),
-                    field_type: field_rev.field_type_rev.into(),
+                    field_type: field_rev.ty.into(),
                 },
                 Some(cell_rev) => cell_rev.try_into()?,
             };
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 cb84a4f8f4..294aff9885 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
@@ -57,7 +57,7 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
 ) -> Result<String, FlowyError> {
     let field_rev = field_rev.as_ref();
     let changeset = changeset.to_string();
-    let field_type = field_rev.field_type_rev.into();
+    let field_type = field_rev.ty.into();
     let s = match field_type {
         FieldType::RichText => RichTextTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
         FieldType::Number => NumberTypeOptionPB::from(field_rev).apply_changeset(changeset.into(), cell_rev),
@@ -76,7 +76,7 @@ pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
 pub fn decode_any_cell_data<T: TryInto<AnyCellData>>(data: T, field_rev: &FieldRevision) -> CellBytes {
     if let Ok(any_cell_data) = data.try_into() {
         let AnyCellData { data, field_type } = any_cell_data;
-        let to_field_type = field_rev.field_type_rev.into();
+        let to_field_type = field_rev.ty.into();
         match try_decode_cell_data(data.into(), field_rev, &field_type, &to_field_type) {
             Ok(cell_bytes) => cell_bytes,
             Err(e) => {
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
index efa0aec03d..0242a5ca43 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs
@@ -33,7 +33,7 @@ impl FieldBuilder {
             id: field.id,
             name: field.name,
             desc: field.desc,
-            field_type_rev: field.field_type.into(),
+            ty: field.field_type.into(),
             frozen: field.frozen,
             visibility: field.visibility,
             width: field.width,
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs
index 98be107941..aef76b9c65 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/date_type_option/date_type_option_entities.rs
@@ -58,12 +58,12 @@ impl std::convert::From<DateChangesetParams> for CellChangesetPB {
             date: params.date,
             time: params.time,
         };
-        let s = serde_json::to_string(&changeset).unwrap();
+        let content = serde_json::to_string(&changeset).unwrap();
         CellChangesetPB {
             grid_id: params.cell_identifier.grid_id,
             row_id: params.cell_identifier.row_id,
             field_id: params.cell_identifier.field_id,
-            content: Some(s),
+            content,
         }
     }
 }
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
index be83975c91..75f654507d 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
@@ -174,7 +174,7 @@ mod tests {
         field_rev: &FieldRevision,
         expected: Vec<SelectOptionPB>,
     ) {
-        let field_type: FieldType = field_rev.field_type_rev.into();
+        let field_type: FieldType = field_rev.ty.into();
         assert_eq!(
             expected,
             type_option
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
index 5538ff24c7..13da2e8359 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/select_option.rs
@@ -123,7 +123,7 @@ where
 }
 
 pub fn select_option_operation(field_rev: &FieldRevision) -> FlowyResult<Box<dyn SelectOptionOperation>> {
-    let field_type: FieldType = field_rev.field_type_rev.into();
+    let field_type: FieldType = field_rev.ty.into();
     match &field_type {
         FieldType::SingleSelect => {
             let type_option = SingleSelectTypeOptionPB::from(field_rev);
@@ -247,12 +247,12 @@ impl std::convert::From<SelectOptionCellChangesetParams> for CellChangesetPB {
             insert_option_id: params.insert_option_id,
             delete_option_id: params.delete_option_id,
         };
-        let s = serde_json::to_string(&changeset).unwrap();
+        let content = serde_json::to_string(&changeset).unwrap();
         CellChangesetPB {
             grid_id: params.cell_identifier.grid_id,
             row_id: params.cell_identifier.row_id,
             field_id: params.cell_identifier.field_id,
-            content: Some(s),
+            content,
         }
     }
 }
diff --git a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
index 803b175a84..287d0c3217 100644
--- a/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/field/type_options/selection_type_option/single_select_type_option.rs
@@ -154,7 +154,7 @@ mod tests {
         field_rev: &FieldRevision,
         expected: Vec<SelectOptionPB>,
     ) {
-        let field_type: FieldType = field_rev.field_type_rev.into();
+        let field_type: FieldType = field_rev.ty.into();
         assert_eq!(
             expected,
             type_option
diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs
index 903779c0e8..cad2998a5f 100644
--- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_cache.rs
@@ -118,7 +118,7 @@ pub(crate) async fn refresh_filter_cache(
             None => {}
             Some((_, field_rev)) => {
                 let filter_id = FilterId::from(field_rev);
-                let field_type: FieldType = field_rev.field_type_rev.into();
+                let field_type: FieldType = field_rev.ty.into();
                 match &field_type {
                     FieldType::RichText => {
                         let _ = cache
@@ -165,7 +165,7 @@ impl std::convert::From<&Arc<FieldRevision>> for FilterId {
     fn from(rev: &Arc<FieldRevision>) -> Self {
         Self {
             field_id: rev.id.clone(),
-            field_type: rev.field_type_rev.into(),
+            field_type: rev.ty.into(),
         }
     }
 }
diff --git a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
index 409c5bc5ba..a2831c0ad9 100644
--- a/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/filter/filter_service.rs
@@ -178,7 +178,7 @@ fn filter_cell(
     cell_rev: &CellRevision,
 ) -> Option<()> {
     let field_rev = field_revs.get(field_id)?;
-    let field_type = FieldType::from(field_rev.field_type_rev);
+    let field_type = FieldType::from(field_rev.ty);
     let field_type_rev = field_type.clone().into();
     let filter_id = FilterId {
         field_id: field_id.to_owned(),
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 7c7ef00c2e..92cf9da550 100644
--- a/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/grid_editor.rs
@@ -142,7 +142,7 @@ impl GridRevisionEditor {
         let field_rev = result.unwrap();
         let _ = self
             .modify(|grid| {
-                let field_type = field_rev.field_type_rev.into();
+                let field_type = field_rev.ty.into();
                 let deserializer = TypeOptionJsonDeserializer(field_type);
                 let changeset = FieldChangesetParams {
                     field_id: field_id.to_owned(),
@@ -181,7 +181,7 @@ impl GridRevisionEditor {
         let field_id = params.field_id.clone();
         let json_deserializer = match self.grid_pad.read().await.get_field_rev(params.field_id.as_str()) {
             None => return Err(ErrorCode::FieldDoesNotExist.into()),
-            Some((_, field_rev)) => TypeOptionJsonDeserializer(field_rev.field_type_rev.into()),
+            Some((_, field_rev)) => TypeOptionJsonDeserializer(field_rev.ty.into()),
         };
 
         let _ = self
@@ -380,10 +380,6 @@ impl GridRevisionEditor {
 
     #[tracing::instrument(level = "trace", skip_all, err)]
     pub async fn update_cell(&self, cell_changeset: CellChangesetPB) -> FlowyResult<()> {
-        if cell_changeset.content.as_ref().is_none() {
-            return Ok(());
-        }
-
         let CellChangesetPB {
             grid_id,
             row_id,
@@ -400,15 +396,15 @@ impl GridRevisionEditor {
                 tracing::trace!("field changeset: id:{} / value:{:?}", &field_id, content);
                 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)?);
+                content = apply_cell_data_changeset(content, cell_rev, field_rev)?;
                 let cell_changeset = CellChangesetPB {
                     grid_id,
                     row_id: row_id.clone(),
-                    field_id,
+                    field_id: field_id.clone(),
                     content,
                 };
                 let _ = self.block_manager.update_cell(cell_changeset).await?;
-                self.view_manager.did_update_row(&row_id).await;
+                self.view_manager.did_update_cell(&row_id, &field_id).await;
                 Ok(())
             }
         }
diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
index ee121913cc..da3ea1be2d 100644
--- a/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
@@ -253,7 +253,7 @@ impl GroupConfigurationDelegate for Arc<RwLock<GridViewRevisionPad>> {
         let view_pad = self.clone();
         wrap_future(async move {
             let grid_pad = view_pad.read().await;
-            let configurations = grid_pad.get_groups(&field_rev.id, &field_rev.field_type_rev);
+            let configurations = grid_pad.get_groups(&field_rev.id, &field_rev.ty);
             match configurations {
                 None => default_group_configuration(&field_rev),
                 Some(mut configurations) => {
diff --git a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
index 17c5e93569..5a8faad6a0 100644
--- a/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
@@ -82,6 +82,10 @@ impl GridViewManager {
         }
     }
 
+    pub(crate) async fn did_update_cell(&self, row_id: &str, _field_id: &str) {
+        self.did_update_row(row_id).await
+    }
+
     pub(crate) async fn did_delete_row(&self, row_rev: Arc<RowRevision>) {
         for view_editor in self.view_editors.iter() {
             view_editor.did_delete_row(&row_rev).await;
diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs
index de9d93bba1..a1d5df2154 100644
--- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/group_controller.rs
@@ -98,6 +98,7 @@ pub struct Group {
 impl std::convert::From<Group> for GroupPB {
     fn from(group: Group) -> Self {
         Self {
+            field_id: group.field_id,
             group_id: group.id,
             desc: group.desc,
             rows: group.rows,
@@ -166,7 +167,7 @@ where
             None => None,
             Some(content) => Some(C::try_from(Bytes::from(content))?),
         };
-        let field_type_rev = field_rev.field_type_rev;
+        let field_type_rev = field_rev.ty;
         let type_option = field_rev.get_type_option_entry::<T>(field_type_rev);
         let groups = G::generate_groups(&field_rev.id, &configuration, &type_option);
 
diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs
index 029c64b578..6e569a7b54 100644
--- a/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/group/group_generator/select_option_group.rs
@@ -213,13 +213,18 @@ fn add_row(
     row_rev: &RowRevision,
 ) {
     cell_data.select_options.iter().for_each(|option| {
-        if option.id == group.id && !group.contains_row(&row_rev.id) {
-            let row_pb = RowPB::from(row_rev);
-            changesets.push(GroupRowsChangesetPB::insert(
-                group.id.clone(),
-                vec![InsertedRowPB::new(row_pb.clone())],
-            ));
-            group.add_row(row_pb);
+        if option.id == group.id {
+            if !group.contains_row(&row_rev.id) {
+                let row_pb = RowPB::from(row_rev);
+                changesets.push(GroupRowsChangesetPB::insert(
+                    group.id.clone(),
+                    vec![InsertedRowPB::new(row_pb.clone())],
+                ));
+                group.add_row(row_pb);
+            }
+        } else if group.contains_row(&row_rev.id) {
+            changesets.push(GroupRowsChangesetPB::delete(group.id.clone(), vec![row_rev.id.clone()]));
+            group.remove_row(&row_rev.id);
         }
     });
 }
diff --git a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs
index 4424b0edcc..0e152bf27c 100644
--- a/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs
+++ b/frontend/rust-lib/flowy-grid/src/services/group/group_service.rs
@@ -46,7 +46,7 @@ impl GroupService {
         row_revs: Vec<Arc<RowRevision>>,
     ) -> Option<Vec<Group>> {
         let field_rev = find_group_field(field_revs)?;
-        let field_type: FieldType = field_rev.field_type_rev.into();
+        let field_type: FieldType = field_rev.ty.into();
         let configuration = self.delegate.get_group_configuration(field_rev.clone()).await;
         match self
             .build_groups(&field_type, &field_rev, row_revs, configuration)
@@ -200,7 +200,7 @@ fn find_group_field(field_revs: &[Arc<FieldRevision>]) -> Option<Arc<FieldRevisi
     let field_rev = field_revs
         .iter()
         .find(|field_rev| {
-            let field_type: FieldType = field_rev.field_type_rev.into();
+            let field_type: FieldType = field_rev.ty.into();
             field_type.can_be_group()
         })
         .cloned();
@@ -208,7 +208,7 @@ fn find_group_field(field_revs: &[Arc<FieldRevision>]) -> Option<Arc<FieldRevisi
 }
 
 pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurationRevision {
-    let field_type: FieldType = field_rev.field_type_rev.into();
+    let field_type: FieldType = field_rev.ty.into();
     let bytes: Bytes = match field_type {
         FieldType::RichText => TextGroupConfigurationPB::default().try_into().unwrap(),
         FieldType::Number => NumberGroupConfigurationPB::default().try_into().unwrap(),
@@ -221,7 +221,7 @@ pub fn default_group_configuration(field_rev: &FieldRevision) -> GroupConfigurat
     GroupConfigurationRevision {
         id: gen_grid_group_id(),
         field_id: field_rev.id.clone(),
-        field_type_rev: field_rev.field_type_rev,
+        field_type_rev: field_rev.ty,
         content: Some(bytes.to_vec()),
     }
 }
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 848b2876fa..06eafe584f 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
@@ -97,7 +97,7 @@ impl<'a> GridRowTestBuilder<'a> {
         self.field_revs
             .iter()
             .find(|field_rev| {
-                let t_field_type: FieldType = field_rev.field_type_rev.into();
+                let t_field_type: FieldType = field_rev.ty.into();
                 &t_field_type == field_type
             })
             .unwrap()
diff --git a/frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs
index ccc779d17a..2d500d2712 100644
--- a/frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs
+++ b/frontend/rust-lib/flowy-grid/tests/grid/cell_test/test.rs
@@ -18,7 +18,7 @@ async fn grid_cell_update() {
     let mut scripts = vec![];
     for (_, row_rev) in row_revs.iter().enumerate() {
         for field_rev in field_revs {
-            let field_type: FieldType = field_rev.field_type_rev.into();
+            let field_type: FieldType = field_rev.ty.into();
             let data = match field_type {
                 FieldType::RichText => "".to_string(),
                 FieldType::Number => "123".to_string(),
@@ -40,7 +40,7 @@ async fn grid_cell_update() {
                     grid_id: block_id.to_string(),
                     row_id: row_rev.id.clone(),
                     field_id: field_rev.id.clone(),
-                    content: Some(data),
+                    content: data,
                 },
                 is_err: false,
             });
diff --git a/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs
index 8615d868c2..fa2f003baa 100644
--- a/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs
+++ b/frontend/rust-lib/flowy-grid/tests/grid/field_test/util.rs
@@ -12,7 +12,7 @@ pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
     let cloned_field_rev = field_rev.clone();
 
     let type_option_data = field_rev
-        .get_type_option_entry::<RichTextTypeOptionPB>(field_rev.field_type_rev)
+        .get_type_option_entry::<RichTextTypeOptionPB>(field_rev.ty)
         .unwrap()
         .protobuf_bytes()
         .to_vec();
@@ -21,7 +21,7 @@ pub fn create_text_field(grid_id: &str) -> (InsertFieldParams, FieldRevision) {
         id: field_rev.id,
         name: field_rev.name,
         desc: field_rev.desc,
-        field_type: field_rev.field_type_rev.into(),
+        field_type: field_rev.ty.into(),
         frozen: field_rev.frozen,
         visibility: field_rev.visibility,
         width: field_rev.width,
@@ -45,7 +45,7 @@ pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRev
     let field_rev = FieldBuilder::new(single_select).name("Name").visibility(true).build();
     let cloned_field_rev = field_rev.clone();
     let type_option_data = field_rev
-        .get_type_option_entry::<SingleSelectTypeOptionPB>(field_rev.field_type_rev)
+        .get_type_option_entry::<SingleSelectTypeOptionPB>(field_rev.ty)
         .unwrap()
         .protobuf_bytes()
         .to_vec();
@@ -54,7 +54,7 @@ pub fn create_single_select_field(grid_id: &str) -> (InsertFieldParams, FieldRev
         id: field_rev.id,
         name: field_rev.name,
         desc: field_rev.desc,
-        field_type: field_rev.field_type_rev.into(),
+        field_type: field_rev.ty.into(),
         frozen: field_rev.frozen,
         visibility: field_rev.visibility,
         width: field_rev.width,
diff --git a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs
index d629b78fb0..650564d8d5 100644
--- a/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs
+++ b/frontend/rust-lib/flowy-grid/tests/grid/filter_test/script.rs
@@ -68,7 +68,7 @@ impl GridFilterTest {
             FilterScript::DeleteGridTableFilter { filter_id, field_rev} => {
                 let layout_type = GridLayout::Table;
                 let params = GridSettingChangesetBuilder::new(&self.grid_id, &layout_type)
-                    .delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.field_type_rev })
+                    .delete_filter(DeleteFilterParams { field_id: field_rev.id, filter_id, field_type_rev: field_rev.ty })
                     .build();
                 let _ = self.editor.update_grid_setting(params).await.unwrap();
             }
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 7300c524bb..dbd5df1484 100644
--- a/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs
+++ b/frontend/rust-lib/flowy-grid/tests/grid/grid_editor.rs
@@ -96,7 +96,7 @@ impl GridEditorTest {
         self.field_revs
             .iter()
             .filter(|field_rev| {
-                let t_field_type: FieldType = field_rev.field_type_rev.into();
+                let t_field_type: FieldType = field_rev.ty.into();
                 t_field_type == field_type
             })
             .collect::<Vec<_>>()
diff --git a/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs b/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs
index 7c3bc3b629..1900e89bff 100644
--- a/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs
+++ b/frontend/rust-lib/flowy-grid/tests/grid/group_test/script.rs
@@ -1,5 +1,7 @@
 use crate::grid::grid_editor::GridEditorTest;
-use flowy_grid::entities::{GroupPB, MoveRowParams, RowPB};
+use flowy_grid::entities::{CreateRowParams, FieldType, GridLayout, GroupPB, MoveRowParams, RowPB};
+use flowy_grid::services::cell::insert_select_option_cell;
+use flowy_grid_data_model::revision::RowChangeset;
 
 pub enum GroupScript {
     AssertGroup {
@@ -7,7 +9,7 @@ pub enum GroupScript {
         row_count: usize,
     },
     AssertGroupCount(usize),
-    AssertGroupRow {
+    AssertRow {
         group_index: usize,
         row_index: usize,
         row: RowPB,
@@ -18,6 +20,18 @@ pub enum GroupScript {
         to_group_index: usize,
         to_row_index: usize,
     },
+    CreateRow {
+        group_index: usize,
+    },
+    DeleteRow {
+        group_index: usize,
+        row_index: usize,
+    },
+    UpdateRow {
+        from_group_index: usize,
+        row_index: usize,
+        to_group_index: usize,
+    },
 }
 
 pub struct GridGroupTest {
@@ -62,7 +76,7 @@ impl GridGroupTest {
 
                 self.editor.move_row(params).await.unwrap();
             }
-            GroupScript::AssertGroupRow {
+            GroupScript::AssertRow {
                 group_index,
                 row_index,
                 row,
@@ -73,6 +87,44 @@ impl GridGroupTest {
 
                 assert_eq!(row.id, compare_row.id);
             }
+            GroupScript::CreateRow { group_index } => {
+                //
+                let group = self.group_at_index(group_index).await;
+                let params = CreateRowParams {
+                    grid_id: self.editor.grid_id.clone(),
+                    start_row_id: None,
+                    group_id: Some(group.group_id.clone()),
+                    layout: GridLayout::Board,
+                };
+                let _ = self.editor.create_row(params).await.unwrap();
+            }
+            GroupScript::DeleteRow { group_index, row_index } => {
+                let row = self.row_at_index(group_index, row_index).await;
+                self.editor.delete_row(&row.id).await.unwrap();
+            }
+            GroupScript::UpdateRow {
+                from_group_index,
+                row_index,
+                to_group_index,
+            } => {
+                let from_group = self.group_at_index(from_group_index).await;
+                let to_group = self.group_at_index(to_group_index).await;
+                let field_id = from_group.field_id;
+                let field_rev = self.editor.get_field_rev(&field_id).await.unwrap();
+                let field_type: FieldType = field_rev.ty.into();
+                let cell_rev = match field_type {
+                    FieldType::SingleSelect => insert_select_option_cell(to_group.group_id.clone(), &field_rev),
+                    FieldType::MultiSelect => insert_select_option_cell(to_group.group_id.clone(), &field_rev),
+                    _ => {
+                        panic!("Unsupported group field type");
+                    }
+                };
+
+                let row_id = self.row_at_index(from_group_index, row_index).await.id;
+                let mut row_changeset = RowChangeset::new(row_id);
+                row_changeset.cell_by_field_id.insert(field_id, cell_rev);
+                self.editor.update_row(row_changeset).await.unwrap();
+            }
         }
     }
 
@@ -80,6 +132,11 @@ impl GridGroupTest {
         let groups = self.editor.load_groups().await.unwrap().items;
         groups.get(index).unwrap().clone()
     }
+
+    pub async fn row_at_index(&self, group_index: usize, row_index: usize) -> RowPB {
+        let groups = self.group_at_index(group_index).await;
+        groups.rows.get(row_index).unwrap().clone()
+    }
 }
 
 impl std::ops::Deref for GridGroupTest {
diff --git a/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs b/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs
index 317c852b5d..798f478439 100644
--- a/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs
+++ b/frontend/rust-lib/flowy-grid/tests/grid/group_test/test.rs
@@ -27,6 +27,7 @@ async fn board_move_row_test() {
     let mut test = GridGroupTest::new().await;
     let group = test.group_at_index(0).await;
     let scripts = vec![
+        // Move the row at 0 in group0 to group1 at 1
         MoveRow {
             from_group_index: 0,
             from_row_index: 0,
@@ -37,7 +38,7 @@ async fn board_move_row_test() {
             group_index: 0,
             row_count: 2,
         },
-        AssertGroupRow {
+        AssertRow {
             group_index: 0,
             row_index: 1,
             row: group.rows.get(0).unwrap().clone(),
@@ -65,7 +66,7 @@ async fn board_move_row_to_other_group_test() {
             group_index: 1,
             row_count: 3,
         },
-        AssertGroupRow {
+        AssertRow {
             group_index: 1,
             row_index: 1,
             row: group.rows.get(0).unwrap().clone(),
@@ -91,7 +92,7 @@ async fn board_move_row_to_other_group_and_reorder_test() {
             to_group_index: 1,
             to_row_index: 2,
         },
-        AssertGroupRow {
+        AssertRow {
             group_index: 1,
             row_index: 2,
             row: group.rows.get(0).unwrap().clone(),
@@ -99,3 +100,102 @@ async fn board_move_row_to_other_group_and_reorder_test() {
     ];
     test.run_scripts(scripts).await;
 }
+
+#[tokio::test]
+async fn board_create_row_test() {
+    let mut test = GridGroupTest::new().await;
+    let scripts = vec![
+        CreateRow { group_index: 0 },
+        AssertGroup {
+            group_index: 0,
+            row_count: 3,
+        },
+        CreateRow { group_index: 1 },
+        CreateRow { group_index: 1 },
+        AssertGroup {
+            group_index: 1,
+            row_count: 4,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn board_delete_row_test() {
+    let mut test = GridGroupTest::new().await;
+    let scripts = vec![
+        DeleteRow {
+            group_index: 0,
+            row_index: 0,
+        },
+        AssertGroup {
+            group_index: 0,
+            row_count: 1,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn board_delete_all_row_test() {
+    let mut test = GridGroupTest::new().await;
+    let scripts = vec![
+        DeleteRow {
+            group_index: 0,
+            row_index: 0,
+        },
+        DeleteRow {
+            group_index: 0,
+            row_index: 0,
+        },
+        AssertGroup {
+            group_index: 0,
+            row_count: 0,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn board_update_row_test() {
+    let mut test = GridGroupTest::new().await;
+    let scripts = vec![
+        // Update the row at 0 in group0 by setting the row's group field data
+        UpdateRow {
+            from_group_index: 0,
+            row_index: 0,
+            to_group_index: 1,
+        },
+        AssertGroup {
+            group_index: 0,
+            row_count: 1,
+        },
+        AssertGroup {
+            group_index: 1,
+            row_count: 3,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn board_reorder_group_test() {
+    let mut test = GridGroupTest::new().await;
+    let scripts = vec![
+        // Update the row at 0 in group0 by setting the row's group field data
+        UpdateRow {
+            from_group_index: 0,
+            row_index: 0,
+            to_group_index: 1,
+        },
+        AssertGroup {
+            group_index: 0,
+            row_count: 1,
+        },
+        AssertGroup {
+            group_index: 1,
+            row_count: 3,
+        },
+    ];
+    test.run_scripts(scripts).await;
+}
diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs
index f8303c973d..fedac5c591 100644
--- a/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs
+++ b/shared-lib/flowy-grid-data-model/src/revision/grid_rev.rs
@@ -94,7 +94,7 @@ pub struct FieldRevision {
     pub desc: String,
 
     #[serde(rename = "field_type")]
-    pub field_type_rev: FieldTypeRevision,
+    pub ty: FieldTypeRevision,
 
     pub frozen: bool,
 
@@ -134,7 +134,7 @@ impl FieldRevision {
             id: gen_field_id(),
             name: name.to_string(),
             desc: desc.to_string(),
-            field_type_rev: field_type.into(),
+            ty: field_type.into(),
             frozen: false,
             visibility: true,
             width,
@@ -147,7 +147,7 @@ impl FieldRevision {
     where
         T: TypeOptionDataEntry + ?Sized,
     {
-        let id = self.field_type_rev.to_string();
+        let id = self.ty.to_string();
         self.type_options.insert(id, entry.json_str());
     }
 
diff --git a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs
index c9d3b034d8..da366f39ab 100644
--- a/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs
+++ b/shared-lib/flowy-grid-data-model/src/revision/grid_setting_rev.rs
@@ -173,7 +173,7 @@ where
         let objects_by_field_id = field_revs
             .iter()
             .flat_map(|field_rev| {
-                let field_type = &field_rev.field_type_rev;
+                let field_type = &field_rev.ty;
                 let field_id = &field_rev.id;
 
                 let object_rev_map = self.inner.get(field_id)?;
diff --git a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs
index 9b471a206b..aad9c8b5f3 100644
--- a/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs
+++ b/shared-lib/flowy-sync/src/client_grid/grid_revision_pad.rs
@@ -155,7 +155,7 @@ impl GridRevisionPad {
                         mut_field_rev.insert_type_option_str(&field_type, type_option_json);
                     }
 
-                    mut_field_rev.field_type_rev = field_type;
+                    mut_field_rev.ty = field_type;
                     Ok(Some(()))
                 }
             }
@@ -181,7 +181,7 @@ impl GridRevisionPad {
             }
 
             if let Some(field_type) = changeset.field_type {
-                field.field_type_rev = field_type;
+                field.ty = field_type;
                 is_changed = Some(())
             }
 
@@ -203,7 +203,7 @@ impl GridRevisionPad {
             if let Some(type_option_data) = changeset.type_option_data {
                 match deserializer.deserialize(type_option_data) {
                     Ok(json_str) => {
-                        let field_type = field.field_type_rev;
+                        let field_type = field.ty;
                         field.insert_type_option_str(&field_type, json_str);
                         is_changed = Some(())
                     }