Browse Source

chore: add grid unit test

appflowy 3 years ago
parent
commit
baa70f2ee6

+ 8 - 12
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pb.dart

@@ -215,7 +215,7 @@ class Field extends $pb.GeneratedMessage {
     ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
     ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
     ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
-    ..aOM<AnyData>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions', subBuilder: AnyData.create)
+    ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions')
     ..hasRequiredFields = false
   ;
 
@@ -228,7 +228,7 @@ class Field extends $pb.GeneratedMessage {
     $core.bool? frozen,
     $core.bool? visibility,
     $core.int? width,
-    AnyData? typeOptions,
+    $core.String? typeOptions,
   }) {
     final _result = create();
     if (id != null) {
@@ -342,15 +342,13 @@ class Field extends $pb.GeneratedMessage {
   void clearWidth() => clearField(7);
 
   @$pb.TagNumber(8)
-  AnyData get typeOptions => $_getN(7);
+  $core.String get typeOptions => $_getSZ(7);
   @$pb.TagNumber(8)
-  set typeOptions(AnyData v) { setField(8, v); }
+  set typeOptions($core.String v) { $_setString(7, v); }
   @$pb.TagNumber(8)
   $core.bool hasTypeOptions() => $_has(7);
   @$pb.TagNumber(8)
   void clearTypeOptions() => clearField(8);
-  @$pb.TagNumber(8)
-  AnyData ensureTypeOptions() => $_ensure(7);
 }
 
 enum FieldChangeset_OneOfName {
@@ -432,7 +430,7 @@ class FieldChangeset extends $pb.GeneratedMessage {
     ..aOB(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'frozen')
     ..aOB(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'visibility')
     ..a<$core.int>(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'width', $pb.PbFieldType.O3)
-    ..aOM<AnyData>(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions', subBuilder: AnyData.create)
+    ..aOS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'typeOptions')
     ..hasRequiredFields = false
   ;
 
@@ -445,7 +443,7 @@ class FieldChangeset extends $pb.GeneratedMessage {
     $core.bool? frozen,
     $core.bool? visibility,
     $core.int? width,
-    AnyData? typeOptions,
+    $core.String? typeOptions,
   }) {
     final _result = create();
     if (fieldId != null) {
@@ -580,15 +578,13 @@ class FieldChangeset extends $pb.GeneratedMessage {
   void clearWidth() => clearField(7);
 
   @$pb.TagNumber(8)
-  AnyData get typeOptions => $_getN(7);
+  $core.String get typeOptions => $_getSZ(7);
   @$pb.TagNumber(8)
-  set typeOptions(AnyData v) { setField(8, v); }
+  set typeOptions($core.String v) { $_setString(7, v); }
   @$pb.TagNumber(8)
   $core.bool hasTypeOptions() => $_has(7);
   @$pb.TagNumber(8)
   void clearTypeOptions() => clearField(8);
-  @$pb.TagNumber(8)
-  AnyData ensureTypeOptions() => $_ensure(7);
 }
 
 class RepeatedField extends $pb.GeneratedMessage {

+ 4 - 4
frontend/app_flowy/packages/flowy_sdk/lib/protobuf/flowy-grid-data-model/meta.pbjson.dart

@@ -69,12 +69,12 @@ const Field$json = const {
     const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '10': 'frozen'},
     const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '10': 'visibility'},
     const {'1': 'width', '3': 7, '4': 1, '5': 5, '10': 'width'},
-    const {'1': 'type_options', '3': 8, '4': 1, '5': 11, '6': '.AnyData', '10': 'typeOptions'},
+    const {'1': 'type_options', '3': 8, '4': 1, '5': 9, '10': 'typeOptions'},
   ],
 };
 
 /// Descriptor for `Field`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIeCgp2aXNpYmlsaXR5GAYgASgIUgp2aXNpYmlsaXR5EhQKBXdpZHRoGAcgASgFUgV3aWR0aBIrCgx0eXBlX29wdGlvbnMYCCABKAsyCC5BbnlEYXRhUgt0eXBlT3B0aW9ucw==');
+final $typed_data.Uint8List fieldDescriptor = $convert.base64Decode('CgVGaWVsZBIOCgJpZBgBIAEoCVICaWQSEgoEbmFtZRgCIAEoCVIEbmFtZRISCgRkZXNjGAMgASgJUgRkZXNjEikKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVSCWZpZWxkVHlwZRIWCgZmcm96ZW4YBSABKAhSBmZyb3plbhIeCgp2aXNpYmlsaXR5GAYgASgIUgp2aXNpYmlsaXR5EhQKBXdpZHRoGAcgASgFUgV3aWR0aBIhCgx0eXBlX29wdGlvbnMYCCABKAlSC3R5cGVPcHRpb25z');
 @$core.Deprecated('Use fieldChangesetDescriptor instead')
 const FieldChangeset$json = const {
   '1': 'FieldChangeset',
@@ -86,7 +86,7 @@ const FieldChangeset$json = const {
     const {'1': 'frozen', '3': 5, '4': 1, '5': 8, '9': 3, '10': 'frozen'},
     const {'1': 'visibility', '3': 6, '4': 1, '5': 8, '9': 4, '10': 'visibility'},
     const {'1': 'width', '3': 7, '4': 1, '5': 5, '9': 5, '10': 'width'},
-    const {'1': 'type_options', '3': 8, '4': 1, '5': 11, '6': '.AnyData', '9': 6, '10': 'typeOptions'},
+    const {'1': 'type_options', '3': 8, '4': 1, '5': 9, '9': 6, '10': 'typeOptions'},
   ],
   '8': const [
     const {'1': 'one_of_name'},
@@ -100,7 +100,7 @@ const FieldChangeset$json = const {
 };
 
 /// Descriptor for `FieldChangeset`. Decode as a `google.protobuf.DescriptorProto`.
-final $typed_data.Uint8List fieldChangesetDescriptor = $convert.base64Decode('Cg5GaWVsZENoYW5nZXNldBIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjEisKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVIAlIJZmllbGRUeXBlEhgKBmZyb3plbhgFIAEoCEgDUgZmcm96ZW4SIAoKdmlzaWJpbGl0eRgGIAEoCEgEUgp2aXNpYmlsaXR5EhYKBXdpZHRoGAcgASgFSAVSBXdpZHRoEi0KDHR5cGVfb3B0aW9ucxgIIAEoCzIILkFueURhdGFIBlILdHlwZU9wdGlvbnNCDQoLb25lX29mX25hbWVCDQoLb25lX29mX2Rlc2NCEwoRb25lX29mX2ZpZWxkX3R5cGVCDwoNb25lX29mX2Zyb3plbkITChFvbmVfb2ZfdmlzaWJpbGl0eUIOCgxvbmVfb2Zfd2lkdGhCFQoTb25lX29mX3R5cGVfb3B0aW9ucw==');
+final $typed_data.Uint8List fieldChangesetDescriptor = $convert.base64Decode('Cg5GaWVsZENoYW5nZXNldBIZCghmaWVsZF9pZBgBIAEoCVIHZmllbGRJZBIUCgRuYW1lGAIgASgJSABSBG5hbWUSFAoEZGVzYxgDIAEoCUgBUgRkZXNjEisKCmZpZWxkX3R5cGUYBCABKA4yCi5GaWVsZFR5cGVIAlIJZmllbGRUeXBlEhgKBmZyb3plbhgFIAEoCEgDUgZmcm96ZW4SIAoKdmlzaWJpbGl0eRgGIAEoCEgEUgp2aXNpYmlsaXR5EhYKBXdpZHRoGAcgASgFSAVSBXdpZHRoEiMKDHR5cGVfb3B0aW9ucxgIIAEoCUgGUgt0eXBlT3B0aW9uc0INCgtvbmVfb2ZfbmFtZUINCgtvbmVfb2ZfZGVzY0ITChFvbmVfb2ZfZmllbGRfdHlwZUIPCg1vbmVfb2ZfZnJvemVuQhMKEW9uZV9vZl92aXNpYmlsaXR5Qg4KDG9uZV9vZl93aWR0aEIVChNvbmVfb2ZfdHlwZV9vcHRpb25z');
 @$core.Deprecated('Use repeatedFieldDescriptor instead')
 const RepeatedField$json = const {
   '1': 'RepeatedField',

+ 2 - 0
frontend/rust-lib/Cargo.lock

@@ -1073,6 +1073,8 @@ dependencies = [
  "rayon",
  "rust_decimal",
  "rusty-money",
+ "serde",
+ "serde_json",
  "strum",
  "strum_macros",
  "tokio",

+ 1 - 1
frontend/rust-lib/flowy-block/src/lib.rs

@@ -12,7 +12,7 @@ pub mod errors {
     pub use flowy_error::{internal_error, ErrorCode, FlowyError};
 }
 
-pub const DOCUMENT_SYNC_INTERVAL_IN_MILLIS: u64 = 1000;
+pub const TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS: u64 = 1000;
 
 use crate::errors::FlowyError;
 use flowy_collaboration::entities::text_block_info::{

+ 2 - 2
frontend/rust-lib/flowy-block/src/web_socket.rs

@@ -1,4 +1,4 @@
-use crate::{queue::EditorCommand, DOCUMENT_SYNC_INTERVAL_IN_MILLIS};
+use crate::{queue::EditorCommand, TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS};
 use bytes::Bytes;
 use flowy_collaboration::{
     entities::{
@@ -36,7 +36,7 @@ pub(crate) async fn make_block_ws_manager(
         RichTextConflictController::new(&user_id, resolver, Arc::new(ws_data_provider.clone()), rev_manager);
     let ws_data_stream = Arc::new(TextBlockRevisionWSDataStream::new(conflict_controller));
     let ws_data_sink = Arc::new(TextBlockWSDataSink(ws_data_provider));
-    let ping_duration = Duration::from_millis(DOCUMENT_SYNC_INTERVAL_IN_MILLIS);
+    let ping_duration = Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS);
     let ws_manager = Arc::new(RevisionWebSocketManager::new(
         "Block",
         &doc_id,

+ 2 - 2
frontend/rust-lib/flowy-block/tests/document/script.rs

@@ -1,5 +1,5 @@
 use flowy_block::editor::ClientTextBlockEditor;
-use flowy_block::DOCUMENT_SYNC_INTERVAL_IN_MILLIS;
+use flowy_block::TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS;
 use flowy_sync::disk::RevisionState;
 use flowy_test::{helper::ViewTest, FlowySDKTest};
 use lib_ot::{core::Interval, rich_text::RichTextDelta};
@@ -80,6 +80,6 @@ impl TextBlockEditorTest {
                 assert_eq!(expected_delta, delta);
             }
         }
-        sleep(Duration::from_millis(DOCUMENT_SYNC_INTERVAL_IN_MILLIS)).await;
+        sleep(Duration::from_millis(TEXT_BLOCK_SYNC_INTERVAL_IN_MILLIS)).await;
     }
 }

+ 2 - 0
frontend/rust-lib/flowy-grid/Cargo.toml

@@ -33,6 +33,8 @@ dashmap = "4.0"
 tokio = {version = "1", features = ["sync"]}
 rayon = "1.5"
 parking_lot = "0.11"
+serde = { version = "1.0", features = ["derive"] }
+serde_json = {version = "1.0"}
 
 [dev-dependencies]
 flowy-test = { path = "../flowy-test" }

+ 1 - 1
frontend/rust-lib/flowy-grid/src/event_handler.rs

@@ -35,7 +35,7 @@ pub(crate) async fn get_fields_handler(
 ) -> DataResult<RepeatedField, FlowyError> {
     let payload: QueryFieldPayload = data.into_inner();
     let editor = manager.get_grid_editor(&payload.grid_id)?;
-    let repeated_field = editor.get_fields(payload.field_orders).await?;
+    let repeated_field = editor.get_fields(Some(payload.field_orders)).await?;
     data_result(repeated_field)
 }
 

+ 5 - 11
frontend/rust-lib/flowy-grid/src/macros.rs

@@ -11,7 +11,7 @@ macro_rules! impl_from_field_type_option {
     ($target: ident) => {
         impl std::convert::From<&Field> for $target {
             fn from(field: &Field) -> $target {
-                match $target::try_from(Bytes::from(field.type_options.value.clone())) {
+                match serde_json::from_str(&field.type_options) {
                     Ok(obj) => obj,
                     Err(err) => {
                         tracing::error!("{} convert from any data failed, {:?}", stringify!($target), err);
@@ -32,19 +32,13 @@ macro_rules! impl_to_field_type_option {
             }
         }
 
-        impl std::convert::From<$target> for AnyData {
+        impl std::convert::From<$target> for String {
             fn from(field_description: $target) -> Self {
-                let field_type = field_description.field_type();
-                match field_description.try_into() {
-                    Ok(bytes) => {
-                        let bytes: Bytes = bytes;
-                        AnyData::from_bytes(field_type, bytes)
-                    }
+                match serde_json::to_string(&field_description) {
+                    Ok(s) => s,
                     Err(e) => {
                         tracing::error!("Field type data convert to AnyData fail, error: {:?}", e);
-                        // it's impossible to fail when unwrapping the default field type data
-                        let default_bytes: Bytes = $target::default().try_into().unwrap();
-                        AnyData::from_bytes(field_type, default_bytes)
+                        serde_json::to_string(&$target::default()).unwrap()
                     }
                 }
             }

+ 7 - 7
frontend/rust-lib/flowy-grid/src/services/field/field_builder.rs

@@ -59,7 +59,7 @@ impl FieldBuilder {
 
 pub trait TypeOptionsBuilder {
     fn field_type(&self) -> FieldType;
-    fn build(&self) -> AnyData;
+    fn build(&self) -> String;
 }
 
 // Text
@@ -76,7 +76,7 @@ impl TypeOptionsBuilder for RichTextTypeOptionsBuilder {
         self.0.field_type()
     }
 
-    fn build(&self) -> AnyData {
+    fn build(&self) -> String {
         self.0.clone().into()
     }
 }
@@ -115,7 +115,7 @@ impl TypeOptionsBuilder for NumberTypeOptionsBuilder {
         self.0.field_type()
     }
 
-    fn build(&self) -> AnyData {
+    fn build(&self) -> String {
         self.0.clone().into()
     }
 }
@@ -142,7 +142,7 @@ impl TypeOptionsBuilder for DateTypeOptionsBuilder {
         self.0.field_type()
     }
 
-    fn build(&self) -> AnyData {
+    fn build(&self) -> String {
         self.0.clone().into()
     }
 }
@@ -165,7 +165,7 @@ impl TypeOptionsBuilder for SingleSelectTypeOptionsBuilder {
         self.0.field_type()
     }
 
-    fn build(&self) -> AnyData {
+    fn build(&self) -> String {
         self.0.clone().into()
     }
 }
@@ -189,7 +189,7 @@ impl TypeOptionsBuilder for MultiSelectTypeOptionsBuilder {
         self.0.field_type()
     }
 
-    fn build(&self) -> AnyData {
+    fn build(&self) -> String {
         self.0.clone().into()
     }
 }
@@ -211,7 +211,7 @@ impl TypeOptionsBuilder for CheckboxTypeOptionsBuilder {
         self.0.field_type()
     }
 
-    fn build(&self) -> AnyData {
+    fn build(&self) -> String {
         self.0.clone().into()
     }
 }

+ 11 - 10
frontend/rust-lib/flowy-grid/src/services/field/type_options.rs

@@ -13,10 +13,11 @@ use rusty_money::{
     iso::{Currency, CNY, EUR, USD},
     Money,
 };
+use serde::{Deserialize, Serialize};
 use std::str::FromStr;
 use strum_macros::EnumIter;
 
-#[derive(Debug, Clone, ProtoBuf, Default)]
+#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct RichTextDescription {
     #[pb(index = 1)]
     pub format: String,
@@ -34,7 +35,7 @@ impl StringifyCellData for RichTextDescription {
 }
 
 // Checkbox
-#[derive(Debug, Clone, ProtoBuf, Default)]
+#[derive(Debug, Clone, Serialize, Deserialize, Default, ProtoBuf)]
 pub struct CheckboxDescription {
     #[pb(index = 1)]
     pub is_selected: bool,
@@ -56,7 +57,7 @@ impl StringifyCellData for CheckboxDescription {
 }
 
 // Date
-#[derive(Clone, Debug, ProtoBuf, Default)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct DateDescription {
     #[pb(index = 1)]
     pub date_format: DateFormat,
@@ -115,7 +116,7 @@ impl StringifyCellData for DateDescription {
     }
 }
 
-#[derive(Clone, Debug, Copy, ProtoBuf_Enum)]
+#[derive(Clone, Debug, Copy, Serialize, Deserialize, ProtoBuf_Enum)]
 pub enum DateFormat {
     Local = 0,
     US = 1,
@@ -158,7 +159,7 @@ impl DateFormat {
     }
 }
 
-#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, ProtoBuf_Enum)]
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash, Serialize, Deserialize, ProtoBuf_Enum)]
 pub enum TimeFormat {
     TwelveHour = 0,
     TwentyFourHour = 1,
@@ -198,7 +199,7 @@ impl std::default::Default for TimeFormat {
 }
 
 // Single select
-#[derive(Clone, Debug, ProtoBuf, Default)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct SingleSelectDescription {
     #[pb(index = 1)]
     pub options: Vec<SelectOption>,
@@ -219,7 +220,7 @@ impl StringifyCellData for SingleSelectDescription {
 }
 
 // Multiple select
-#[derive(Clone, Debug, ProtoBuf, Default)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct MultiSelectDescription {
     #[pb(index = 1)]
     pub options: Vec<SelectOption>,
@@ -238,7 +239,7 @@ impl StringifyCellData for MultiSelectDescription {
     }
 }
 
-#[derive(Clone, Debug, ProtoBuf, Default)]
+#[derive(Clone, Debug, Default, Serialize, Deserialize, ProtoBuf)]
 pub struct SelectOption {
     #[pb(index = 1)]
     pub id: String,
@@ -261,7 +262,7 @@ impl SelectOption {
 }
 
 // Number
-#[derive(Clone, Debug, ProtoBuf)]
+#[derive(Clone, Debug, Serialize, Deserialize, ProtoBuf)]
 pub struct NumberDescription {
     #[pb(index = 1)]
     pub money: MoneySymbol,
@@ -342,7 +343,7 @@ impl StringifyCellData for NumberDescription {
     }
 }
 
-#[derive(Clone, Copy, Debug, EnumIter, ProtoBuf_Enum)]
+#[derive(Clone, Copy, Debug, EnumIter, Serialize, Deserialize, ProtoBuf_Enum)]
 pub enum MoneySymbol {
     CNY = 0,
     EUR = 1,

+ 7 - 2
frontend/rust-lib/flowy-grid/src/services/grid_editor.rs

@@ -9,7 +9,7 @@ use flowy_collaboration::entities::revision::Revision;
 use flowy_collaboration::util::make_delta_from_revisions;
 use flowy_error::{FlowyError, FlowyResult};
 use flowy_grid_data_model::entities::{
-    Field, Grid, GridBlock, RepeatedField, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder,
+    Field, FieldChangeset, Grid, GridBlock, RepeatedField, RepeatedFieldOrder, RepeatedRow, RepeatedRowOrder,
 };
 use flowy_sync::disk::SQLiteGridBlockMetaRevisionPersistence;
 use flowy_sync::{
@@ -60,6 +60,11 @@ impl ClientGridEditor {
         Ok(())
     }
 
+    pub async fn update_field(&self, change: FieldChangeset) -> FlowyResult<()> {
+        let _ = self.modify(|grid| Ok(grid.update_field(change)?)).await?;
+        Ok(())
+    }
+
     pub async fn delete_field(&self, field_id: &str) -> FlowyResult<()> {
         let _ = self.modify(|grid| Ok(grid.delete_field(field_id)?)).await?;
         Ok(())
@@ -81,7 +86,7 @@ impl ClientGridEditor {
         todo!()
     }
 
-    pub async fn get_fields(&self, field_orders: RepeatedFieldOrder) -> FlowyResult<RepeatedField> {
+    pub async fn get_fields(&self, field_orders: Option<RepeatedFieldOrder>) -> FlowyResult<RepeatedField> {
         let fields = self.grid_meta_pad.read().await.get_fields(field_orders)?;
         Ok(fields)
     }

+ 87 - 3
frontend/rust-lib/flowy-grid/tests/grid/grid_test.rs

@@ -1,14 +1,98 @@
 use crate::grid::script::EditorScript::*;
 use crate::grid::script::*;
+use flowy_grid::services::field::{SelectOption, SingleSelectDescription};
+use flowy_grid_data_model::entities::FieldChangeset;
 
 #[tokio::test]
-async fn grid_creat_field_test() {
+async fn default_grid_test() {
+    let scripts = vec![AssertFieldCount(2), AssertGridMetaPad];
+    GridEditorTest::new().await.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_create_field() {
+    let text_field = create_text_field();
+    let single_select_field = create_single_select_field();
     let scripts = vec![
+        AssertFieldCount(2),
         CreateField {
-            field: create_text_field(),
+            field: text_field.clone(),
+        },
+        AssertFieldEqual {
+            field_index: 2,
+            field: text_field,
+        },
+        AssertFieldCount(3),
+        CreateField {
+            field: single_select_field.clone(),
+        },
+        AssertFieldEqual {
+            field_index: 3,
+            field: single_select_field,
         },
+        AssertFieldCount(4),
+    ];
+    GridEditorTest::new().await.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_update_field_with_empty_change() {
+    let single_select_field = create_single_select_field();
+    let change = FieldChangeset {
+        field_id: single_select_field.id.clone(),
+        name: None,
+        desc: None,
+        field_type: None,
+        frozen: None,
+        visibility: None,
+        width: None,
+        type_options: None,
+    };
+
+    let scripts = vec![
         CreateField {
-            field: create_single_select_field(),
+            field: single_select_field.clone(),
+        },
+        UpdateField { change },
+        AssertFieldEqual {
+            field_index: 2,
+            field: single_select_field,
+        },
+    ];
+    GridEditorTest::new().await.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn grid_update_field() {
+    let single_select_field = create_single_select_field();
+    let mut cloned_field = single_select_field.clone();
+
+    let mut single_select_type_options = SingleSelectDescription::from(&single_select_field);
+    single_select_type_options.options.push(SelectOption::new("Unknown"));
+
+    let change = FieldChangeset {
+        field_id: single_select_field.id.clone(),
+        name: None,
+        desc: None,
+        field_type: None,
+        frozen: Some(true),
+        visibility: None,
+        width: Some(1000),
+        type_options: Some(single_select_type_options.clone().into()),
+    };
+
+    cloned_field.frozen = true;
+    cloned_field.width = 1000;
+    cloned_field.type_options = single_select_type_options.into();
+
+    let scripts = vec![
+        CreateField {
+            field: single_select_field.clone(),
+        },
+        UpdateField { change },
+        AssertFieldEqual {
+            field_index: 2,
+            field: cloned_field,
         },
         AssertGridMetaPad,
     ];

+ 21 - 3
frontend/rust-lib/flowy-grid/tests/grid/script.rs

@@ -1,15 +1,21 @@
 use flowy_grid::services::field::*;
 use flowy_grid::services::grid_editor::{ClientGridEditor, GridPadBuilder};
-use flowy_grid_data_model::entities::{AnyData, Field, FieldType};
+use flowy_grid_data_model::entities::{AnyData, Field, FieldChangeset, FieldType};
+use flowy_sync::REVISION_WRITE_INTERVAL_IN_MILLIS;
 use flowy_test::event_builder::FolderEventBuilder;
 use flowy_test::helper::ViewTest;
 use flowy_test::FlowySDKTest;
 use std::sync::Arc;
+use std::time::Duration;
+use tokio::time::sleep;
 
 pub enum EditorScript {
     CreateField { field: Field },
-    CreateRow,
+    UpdateField { change: FieldChangeset },
+    AssertFieldCount(usize),
+    AssertFieldEqual { field_index: usize, field: Field },
     AssertGridMetaPad,
+    CreateRow,
 }
 
 pub struct GridEditorTest {
@@ -44,12 +50,24 @@ impl GridEditorTest {
             EditorScript::CreateField { field } => {
                 self.editor.create_field(field).await.unwrap();
             }
-            EditorScript::CreateRow => {}
+            EditorScript::UpdateField { change } => {
+                self.editor.update_field(change).await.unwrap();
+            }
+            EditorScript::AssertFieldCount(count) => {
+                assert_eq!(self.editor.get_fields(None).await.unwrap().len(), count);
+            }
+            EditorScript::AssertFieldEqual { field_index, field } => {
+                let repeated_fields = self.editor.get_fields(None).await.unwrap();
+                let compared_field = repeated_fields[field_index].clone();
+                assert_eq!(compared_field, field);
+            }
             EditorScript::AssertGridMetaPad => {
+                sleep(Duration::from_millis(2 * REVISION_WRITE_INTERVAL_IN_MILLIS)).await;
                 let mut grid_rev_manager = grid_manager.make_grid_rev_manager(&self.grid_id, pool.clone()).unwrap();
                 let grid_pad = grid_rev_manager.load::<GridPadBuilder>(None).await.unwrap();
                 println!("{}", grid_pad.delta_str());
             }
+            EditorScript::CreateRow => {}
         }
     }
 }

+ 24 - 19
shared-lib/flowy-collaboration/src/client_grid/grid_pad.rs

@@ -51,25 +51,30 @@ impl GridMetaPad {
         })
     }
 
-    pub fn get_fields(&self, field_orders: RepeatedFieldOrder) -> CollaborateResult<RepeatedField> {
-        let field_by_field_id = self
-            .grid_meta
-            .fields
-            .iter()
-            .map(|field| (&field.id, field))
-            .collect::<HashMap<&String, &Field>>();
-
-        let fields = field_orders
-            .iter()
-            .flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
-                None => {
-                    tracing::error!("Can't find the field with id: {}", field_order.field_id);
-                    None
-                }
-                Some(field) => Some((*field).clone()),
-            })
-            .collect::<Vec<Field>>();
-        Ok(fields.into())
+    pub fn get_fields(&self, field_orders: Option<RepeatedFieldOrder>) -> CollaborateResult<RepeatedField> {
+        match field_orders {
+            None => Ok(self.grid_meta.fields.clone().into()),
+            Some(field_orders) => {
+                let field_by_field_id = self
+                    .grid_meta
+                    .fields
+                    .iter()
+                    .map(|field| (&field.id, field))
+                    .collect::<HashMap<&String, &Field>>();
+
+                let fields = field_orders
+                    .iter()
+                    .flat_map(|field_order| match field_by_field_id.get(&field_order.field_id) {
+                        None => {
+                            tracing::error!("Can't find the field with id: {}", field_order.field_id);
+                            None
+                        }
+                        Some(field) => Some((*field).clone()),
+                    })
+                    .collect::<Vec<Field>>();
+                Ok(fields.into())
+            }
+        }
     }
 
     pub fn update_field(&mut self, change: FieldChangeset) -> CollaborateResult<Option<GridChange>> {

+ 3 - 3
shared-lib/flowy-grid-data-model/src/entities/meta.rs

@@ -54,7 +54,7 @@ pub struct GridBlockMeta {
     pub rows: Vec<RowMeta>,
 }
 
-#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf)]
+#[derive(Debug, Clone, Default, Serialize, Deserialize, ProtoBuf, PartialEq, Eq)]
 pub struct Field {
     #[pb(index = 1)]
     pub id: String,
@@ -78,7 +78,7 @@ pub struct Field {
     pub width: i32,
 
     #[pb(index = 8)]
-    pub type_options: AnyData,
+    pub type_options: String,
 }
 
 impl Field {
@@ -120,7 +120,7 @@ pub struct FieldChangeset {
     pub width: Option<i32>,
 
     #[pb(index = 8, one_of)]
-    pub type_options: Option<AnyData>,
+    pub type_options: Option<String>,
 }
 
 #[derive(Debug, Default, ProtoBuf)]

+ 64 - 88
shared-lib/flowy-grid-data-model/src/protobuf/model/meta.rs

@@ -727,7 +727,7 @@ pub struct Field {
     pub frozen: bool,
     pub visibility: bool,
     pub width: i32,
-    pub type_options: ::protobuf::SingularPtrField<AnyData>,
+    pub type_options: ::std::string::String,
     // special fields
     pub unknown_fields: ::protobuf::UnknownFields,
     pub cached_size: ::protobuf::CachedSize,
@@ -882,47 +882,35 @@ impl Field {
         self.width = v;
     }
 
-    // .AnyData type_options = 8;
+    // string type_options = 8;
 
 
-    pub fn get_type_options(&self) -> &AnyData {
-        self.type_options.as_ref().unwrap_or_else(|| <AnyData as ::protobuf::Message>::default_instance())
+    pub fn get_type_options(&self) -> &str {
+        &self.type_options
     }
     pub fn clear_type_options(&mut self) {
         self.type_options.clear();
     }
 
-    pub fn has_type_options(&self) -> bool {
-        self.type_options.is_some()
-    }
-
     // Param is passed by value, moved
-    pub fn set_type_options(&mut self, v: AnyData) {
-        self.type_options = ::protobuf::SingularPtrField::some(v);
+    pub fn set_type_options(&mut self, v: ::std::string::String) {
+        self.type_options = v;
     }
 
     // Mutable pointer to the field.
     // If field is not initialized, it is initialized with default value first.
-    pub fn mut_type_options(&mut self) -> &mut AnyData {
-        if self.type_options.is_none() {
-            self.type_options.set_default();
-        }
-        self.type_options.as_mut().unwrap()
+    pub fn mut_type_options(&mut self) -> &mut ::std::string::String {
+        &mut self.type_options
     }
 
     // Take field
-    pub fn take_type_options(&mut self) -> AnyData {
-        self.type_options.take().unwrap_or_else(|| AnyData::new())
+    pub fn take_type_options(&mut self) -> ::std::string::String {
+        ::std::mem::replace(&mut self.type_options, ::std::string::String::new())
     }
 }
 
 impl ::protobuf::Message for Field {
     fn is_initialized(&self) -> bool {
-        for v in &self.type_options {
-            if !v.is_initialized() {
-                return false;
-            }
-        };
         true
     }
 
@@ -964,7 +952,7 @@ impl ::protobuf::Message for Field {
                     self.width = tmp;
                 },
                 8 => {
-                    ::protobuf::rt::read_singular_message_into(wire_type, is, &mut self.type_options)?;
+                    ::protobuf::rt::read_singular_proto3_string_into(wire_type, is, &mut self.type_options)?;
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -999,9 +987,8 @@ impl ::protobuf::Message for Field {
         if self.width != 0 {
             my_size += ::protobuf::rt::value_size(7, self.width, ::protobuf::wire_format::WireTypeVarint);
         }
-        if let Some(ref v) = self.type_options.as_ref() {
-            let len = v.compute_size();
-            my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+        if !self.type_options.is_empty() {
+            my_size += ::protobuf::rt::string_size(8, &self.type_options);
         }
         my_size += ::protobuf::rt::unknown_fields_size(self.get_unknown_fields());
         self.cached_size.set(my_size);
@@ -1030,10 +1017,8 @@ impl ::protobuf::Message for Field {
         if self.width != 0 {
             os.write_int32(7, self.width)?;
         }
-        if let Some(ref v) = self.type_options.as_ref() {
-            os.write_tag(8, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-            os.write_raw_varint32(v.get_cached_size())?;
-            v.write_to_with_cached_sizes(os)?;
+        if !self.type_options.is_empty() {
+            os.write_string(8, &self.type_options)?;
         }
         os.write_unknown_fields(self.get_unknown_fields())?;
         ::std::result::Result::Ok(())
@@ -1108,7 +1093,7 @@ impl ::protobuf::Message for Field {
                 |m: &Field| { &m.width },
                 |m: &mut Field| { &mut m.width },
             ));
-            fields.push(::protobuf::reflect::accessor::make_singular_ptr_field_accessor::<_, ::protobuf::types::ProtobufTypeMessage<AnyData>>(
+            fields.push(::protobuf::reflect::accessor::make_simple_field_accessor::<_, ::protobuf::types::ProtobufTypeString>(
                 "type_options",
                 |m: &Field| { &m.type_options },
                 |m: &mut Field| { &mut m.type_options },
@@ -1208,7 +1193,7 @@ pub enum FieldChangeset_oneof_one_of_width {
 
 #[derive(Clone,PartialEq,Debug)]
 pub enum FieldChangeset_oneof_one_of_type_options {
-    type_options(AnyData),
+    type_options(::std::string::String),
 }
 
 impl FieldChangeset {
@@ -1440,13 +1425,13 @@ impl FieldChangeset {
         self.one_of_width = ::std::option::Option::Some(FieldChangeset_oneof_one_of_width::width(v))
     }
 
-    // .AnyData type_options = 8;
+    // string type_options = 8;
 
 
-    pub fn get_type_options(&self) -> &AnyData {
+    pub fn get_type_options(&self) -> &str {
         match self.one_of_type_options {
             ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(ref v)) => v,
-            _ => <AnyData as ::protobuf::Message>::default_instance(),
+            _ => "",
         }
     }
     pub fn clear_type_options(&mut self) {
@@ -1461,15 +1446,15 @@ impl FieldChangeset {
     }
 
     // Param is passed by value, moved
-    pub fn set_type_options(&mut self, v: AnyData) {
+    pub fn set_type_options(&mut self, v: ::std::string::String) {
         self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(v))
     }
 
     // Mutable pointer to the field.
-    pub fn mut_type_options(&mut self) -> &mut AnyData {
+    pub fn mut_type_options(&mut self) -> &mut ::std::string::String {
         if let ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(_)) = self.one_of_type_options {
         } else {
-            self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(AnyData::new()));
+            self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(::std::string::String::new()));
         }
         match self.one_of_type_options {
             ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(ref mut v)) => v,
@@ -1478,25 +1463,20 @@ impl FieldChangeset {
     }
 
     // Take field
-    pub fn take_type_options(&mut self) -> AnyData {
+    pub fn take_type_options(&mut self) -> ::std::string::String {
         if self.has_type_options() {
             match self.one_of_type_options.take() {
                 ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(v)) => v,
                 _ => panic!(),
             }
         } else {
-            AnyData::new()
+            ::std::string::String::new()
         }
     }
 }
 
 impl ::protobuf::Message for FieldChangeset {
     fn is_initialized(&self) -> bool {
-        if let Some(FieldChangeset_oneof_one_of_type_options::type_options(ref v)) = self.one_of_type_options {
-            if !v.is_initialized() {
-                return false;
-            }
-        }
         true
     }
 
@@ -1547,7 +1527,7 @@ impl ::protobuf::Message for FieldChangeset {
                     if wire_type != ::protobuf::wire_format::WireTypeLengthDelimited {
                         return ::std::result::Result::Err(::protobuf::rt::unexpected_wire_type(wire_type));
                     }
-                    self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(is.read_message()?));
+                    self.one_of_type_options = ::std::option::Option::Some(FieldChangeset_oneof_one_of_type_options::type_options(is.read_string()?));
                 },
                 _ => {
                     ::protobuf::rt::read_unknown_or_skip_group(field_number, wire_type, is, self.mut_unknown_fields())?;
@@ -1609,8 +1589,7 @@ impl ::protobuf::Message for FieldChangeset {
         if let ::std::option::Option::Some(ref v) = self.one_of_type_options {
             match v {
                 &FieldChangeset_oneof_one_of_type_options::type_options(ref v) => {
-                    let len = v.compute_size();
-                    my_size += 1 + ::protobuf::rt::compute_raw_varint32_size(len) + len;
+                    my_size += ::protobuf::rt::string_size(8, &v);
                 },
             };
         }
@@ -1668,9 +1647,7 @@ impl ::protobuf::Message for FieldChangeset {
         if let ::std::option::Option::Some(ref v) = self.one_of_type_options {
             match v {
                 &FieldChangeset_oneof_one_of_type_options::type_options(ref v) => {
-                    os.write_tag(8, ::protobuf::wire_format::WireTypeLengthDelimited)?;
-                    os.write_raw_varint32(v.get_cached_size())?;
-                    v.write_to_with_cached_sizes(os)?;
+                    os.write_string(8, v)?;
                 },
             };
         }
@@ -1747,7 +1724,7 @@ impl ::protobuf::Message for FieldChangeset {
                 FieldChangeset::has_width,
                 FieldChangeset::get_width,
             ));
-            fields.push(::protobuf::reflect::accessor::make_singular_message_accessor::<_, AnyData>(
+            fields.push(::protobuf::reflect::accessor::make_singular_string_accessor::<_>(
                 "type_options",
                 FieldChangeset::has_type_options,
                 FieldChangeset::get_type_options,
@@ -3183,46 +3160,45 @@ static file_descriptor_proto_data: &'static [u8] = b"\
     ndex\x18\x02\x20\x01(\x05R\rstartRowIndex\x12\x1b\n\trow_count\x18\x03\
     \x20\x01(\x05R\x08rowCount\"H\n\rGridBlockMeta\x12\x19\n\x08block_id\x18\
     \x01\x20\x01(\tR\x07blockId\x12\x1c\n\x04rows\x18\x02\x20\x03(\x0b2\x08.\
-    RowMetaR\x04rows\"\xe5\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\x01(\t\
+    RowMetaR\x04rows\"\xdb\x01\n\x05Field\x12\x0e\n\x02id\x18\x01\x20\x01(\t\
     R\x02id\x12\x12\n\x04name\x18\x02\x20\x01(\tR\x04name\x12\x12\n\x04desc\
     \x18\x03\x20\x01(\tR\x04desc\x12)\n\nfield_type\x18\x04\x20\x01(\x0e2\n.\
     FieldTypeR\tfieldType\x12\x16\n\x06frozen\x18\x05\x20\x01(\x08R\x06froze\
     n\x12\x1e\n\nvisibility\x18\x06\x20\x01(\x08R\nvisibility\x12\x14\n\x05w\
-    idth\x18\x07\x20\x01(\x05R\x05width\x12+\n\x0ctype_options\x18\x08\x20\
-    \x01(\x0b2\x08.AnyDataR\x0btypeOptions\"\x87\x03\n\x0eFieldChangeset\x12\
-    \x19\n\x08field_id\x18\x01\x20\x01(\tR\x07fieldId\x12\x14\n\x04name\x18\
-    \x02\x20\x01(\tH\0R\x04name\x12\x14\n\x04desc\x18\x03\x20\x01(\tH\x01R\
-    \x04desc\x12+\n\nfield_type\x18\x04\x20\x01(\x0e2\n.FieldTypeH\x02R\tfie\
-    ldType\x12\x18\n\x06frozen\x18\x05\x20\x01(\x08H\x03R\x06frozen\x12\x20\
-    \n\nvisibility\x18\x06\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\
-    \x18\x07\x20\x01(\x05H\x05R\x05width\x12-\n\x0ctype_options\x18\x08\x20\
-    \x01(\x0b2\x08.AnyDataH\x06R\x0btypeOptionsB\r\n\x0bone_of_nameB\r\n\x0b\
-    one_of_descB\x13\n\x11one_of_field_typeB\x0f\n\rone_of_frozenB\x13\n\x11\
-    one_of_visibilityB\x0e\n\x0cone_of_widthB\x15\n\x13one_of_type_options\"\
-    -\n\rRepeatedField\x12\x1c\n\x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\
-    \x05items\"8\n\x07AnyData\x12\x17\n\x07type_id\x18\x01\x20\x01(\tR\x06ty\
-    peId\x12\x14\n\x05value\x18\x02\x20\x01(\x0cR\x05value\"\xff\x01\n\x07Ro\
-    wMeta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x19\n\x08block_id\
-    \x18\x02\x20\x01(\tR\x07blockId\x12D\n\x10cell_by_field_id\x18\x03\x20\
-    \x03(\x0b2\x1b.RowMeta.CellByFieldIdEntryR\rcellByFieldId\x12\x16\n\x06h\
-    eight\x18\x04\x20\x01(\x05R\x06height\x12\x1e\n\nvisibility\x18\x05\x20\
-    \x01(\x08R\nvisibility\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\
-    \x01\x20\x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellM\
-    etaR\x05value:\x028\x01\"\xa7\x02\n\x10RowMetaChangeset\x12\x15\n\x06row\
-    _id\x18\x01\x20\x01(\tR\x05rowId\x12\x18\n\x06height\x18\x02\x20\x01(\
-    \x05H\0R\x06height\x12\x20\n\nvisibility\x18\x03\x20\x01(\x08H\x01R\nvis\
-    ibility\x12M\n\x10cell_by_field_id\x18\x04\x20\x03(\x0b2$.RowMetaChanges\
-    et.CellByFieldIdEntryR\rcellByFieldId\x1aK\n\x12CellByFieldIdEntry\x12\
-    \x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\
-    \x01(\x0b2\t.CellMetaR\x05value:\x028\x01B\x0f\n\rone_of_heightB\x13\n\
-    \x11one_of_visibility\"\x82\x01\n\x08CellMeta\x12\x0e\n\x02id\x18\x01\
-    \x20\x01(\tR\x02id\x12\x15\n\x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\
-    \x19\n\x08field_id\x18\x03\x20\x01(\tR\x07fieldId\x12\x1c\n\x04data\x18\
-    \x04\x20\x01(\x0b2\x08.AnyDataR\x04data\x12\x16\n\x06height\x18\x05\x20\
-    \x01(\x05R\x06height*d\n\tFieldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\
-    \x06Number\x10\x01\x12\x0c\n\x08DateTime\x10\x02\x12\x10\n\x0cSingleSele\
-    ct\x10\x03\x12\x0f\n\x0bMultiSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\
-    \x05b\x06proto3\
+    idth\x18\x07\x20\x01(\x05R\x05width\x12!\n\x0ctype_options\x18\x08\x20\
+    \x01(\tR\x0btypeOptions\"\xfd\x02\n\x0eFieldChangeset\x12\x19\n\x08field\
+    _id\x18\x01\x20\x01(\tR\x07fieldId\x12\x14\n\x04name\x18\x02\x20\x01(\tH\
+    \0R\x04name\x12\x14\n\x04desc\x18\x03\x20\x01(\tH\x01R\x04desc\x12+\n\nf\
+    ield_type\x18\x04\x20\x01(\x0e2\n.FieldTypeH\x02R\tfieldType\x12\x18\n\
+    \x06frozen\x18\x05\x20\x01(\x08H\x03R\x06frozen\x12\x20\n\nvisibility\
+    \x18\x06\x20\x01(\x08H\x04R\nvisibility\x12\x16\n\x05width\x18\x07\x20\
+    \x01(\x05H\x05R\x05width\x12#\n\x0ctype_options\x18\x08\x20\x01(\tH\x06R\
+    \x0btypeOptionsB\r\n\x0bone_of_nameB\r\n\x0bone_of_descB\x13\n\x11one_of\
+    _field_typeB\x0f\n\rone_of_frozenB\x13\n\x11one_of_visibilityB\x0e\n\x0c\
+    one_of_widthB\x15\n\x13one_of_type_options\"-\n\rRepeatedField\x12\x1c\n\
+    \x05items\x18\x01\x20\x03(\x0b2\x06.FieldR\x05items\"8\n\x07AnyData\x12\
+    \x17\n\x07type_id\x18\x01\x20\x01(\tR\x06typeId\x12\x14\n\x05value\x18\
+    \x02\x20\x01(\x0cR\x05value\"\xff\x01\n\x07RowMeta\x12\x0e\n\x02id\x18\
+    \x01\x20\x01(\tR\x02id\x12\x19\n\x08block_id\x18\x02\x20\x01(\tR\x07bloc\
+    kId\x12D\n\x10cell_by_field_id\x18\x03\x20\x03(\x0b2\x1b.RowMeta.CellByF\
+    ieldIdEntryR\rcellByFieldId\x12\x16\n\x06height\x18\x04\x20\x01(\x05R\
+    \x06height\x12\x1e\n\nvisibility\x18\x05\x20\x01(\x08R\nvisibility\x1aK\
+    \n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\x01(\tR\x03key\x12\
+    \x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05value:\x028\x01\"\
+    \xa7\x02\n\x10RowMetaChangeset\x12\x15\n\x06row_id\x18\x01\x20\x01(\tR\
+    \x05rowId\x12\x18\n\x06height\x18\x02\x20\x01(\x05H\0R\x06height\x12\x20\
+    \n\nvisibility\x18\x03\x20\x01(\x08H\x01R\nvisibility\x12M\n\x10cell_by_\
+    field_id\x18\x04\x20\x03(\x0b2$.RowMetaChangeset.CellByFieldIdEntryR\rce\
+    llByFieldId\x1aK\n\x12CellByFieldIdEntry\x12\x10\n\x03key\x18\x01\x20\
+    \x01(\tR\x03key\x12\x1f\n\x05value\x18\x02\x20\x01(\x0b2\t.CellMetaR\x05\
+    value:\x028\x01B\x0f\n\rone_of_heightB\x13\n\x11one_of_visibility\"\x82\
+    \x01\n\x08CellMeta\x12\x0e\n\x02id\x18\x01\x20\x01(\tR\x02id\x12\x15\n\
+    \x06row_id\x18\x02\x20\x01(\tR\x05rowId\x12\x19\n\x08field_id\x18\x03\
+    \x20\x01(\tR\x07fieldId\x12\x1c\n\x04data\x18\x04\x20\x01(\x0b2\x08.AnyD\
+    ataR\x04data\x12\x16\n\x06height\x18\x05\x20\x01(\x05R\x06height*d\n\tFi\
+    eldType\x12\x0c\n\x08RichText\x10\0\x12\n\n\x06Number\x10\x01\x12\x0c\n\
+    \x08DateTime\x10\x02\x12\x10\n\x0cSingleSelect\x10\x03\x12\x0f\n\x0bMult\
+    iSelect\x10\x04\x12\x0c\n\x08Checkbox\x10\x05b\x06proto3\
 ";
 
 static file_descriptor_proto_lazy: ::protobuf::rt::LazyV2<::protobuf::descriptor::FileDescriptorProto> = ::protobuf::rt::LazyV2::INIT;

+ 2 - 2
shared-lib/flowy-grid-data-model/src/protobuf/proto/meta.proto

@@ -22,7 +22,7 @@ message Field {
     bool frozen = 5;
     bool visibility = 6;
     int32 width = 7;
-    AnyData type_options = 8;
+    string type_options = 8;
 }
 message FieldChangeset {
     string field_id = 1;
@@ -32,7 +32,7 @@ message FieldChangeset {
     oneof one_of_frozen { bool frozen = 5; };
     oneof one_of_visibility { bool visibility = 6; };
     oneof one_of_width { int32 width = 7; };
-    oneof one_of_type_options { AnyData type_options = 8; };
+    oneof one_of_type_options { string type_options = 8; };
 }
 message RepeatedField {
     repeated Field items = 1;