浏览代码

chore: add fitler bloc test

nathan 2 年之前
父节点
当前提交
0ee27097ce
共有 31 个文件被更改,包括 578 次插入92 次删除
  1. 1 1
      frontend/app_flowy/lib/plugins/board/application/group_controller.dart
  2. 2 2
      frontend/app_flowy/lib/plugins/board/application/group_listener.dart
  3. 206 0
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_bloc.dart
  4. 53 0
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart
  5. 48 1
      frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart
  6. 64 0
      frontend/app_flowy/test/bloc_test/grid_test/filter_bloc_test.dart
  7. 6 0
      frontend/app_flowy/test/bloc_test/grid_test/util.dart
  8. 1 0
      frontend/rust-lib/flowy-grid/src/dart_notification.rs
  9. 2 2
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs
  10. 2 2
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs
  11. 31 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/filter_changeset.rs
  12. 2 0
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs
  13. 2 2
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs
  14. 2 2
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs
  15. 2 2
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs
  16. 29 9
      frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs
  17. 3 3
      frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs
  18. 2 3
      frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs
  19. 13 0
      frontend/rust-lib/flowy-grid/src/event_handler.rs
  20. 4 0
      frontend/rust-lib/flowy-grid/src/event_map.rs
  21. 6 6
      frontend/rust-lib/flowy-grid/src/services/filter/controller.rs
  22. 38 16
      frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs
  23. 1 1
      frontend/rust-lib/flowy-grid/src/services/grid_view_manager.rs
  24. 11 7
      frontend/rust-lib/flowy-grid/src/services/group/action.rs
  25. 8 8
      frontend/rust-lib/flowy-grid/src/services/group/controller.rs
  26. 10 6
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs
  27. 4 4
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs
  28. 8 4
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs
  29. 8 4
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs
  30. 7 7
      frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs
  31. 2 0
      shared-lib/grid-rev-model/src/filter_rev.rs

+ 1 - 1
frontend/app_flowy/lib/plugins/board/application/group_controller.dart

@@ -39,7 +39,7 @@ class GroupController {
   void startListening() {
     _listener.start(onGroupChanged: (result) {
       result.fold(
-        (GroupChangesetPB changeset) {
+        (GroupRowsNotificationPB changeset) {
           for (final deletedRow in changeset.deletedRows) {
             group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
             delegate.removeRow(group, deletedRow);

+ 2 - 2
frontend/app_flowy/lib/plugins/board/application/group_listener.dart

@@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
 
-typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>;
+typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
 
 class GroupListener {
   final GroupPB group;
@@ -34,7 +34,7 @@ class GroupListener {
       case GridNotification.DidUpdateGroup:
         result.fold(
           (payload) => _groupNotifier?.value =
-              left(GroupChangesetPB.fromBuffer(payload)),
+              left(GroupRowsNotificationPB.fromBuffer(payload)),
           (error) => _groupNotifier?.value = right(error),
         );
         break;

+ 206 - 0
frontend/app_flowy/lib/plugins/grid/application/filter/filter_bloc.dart

@@ -0,0 +1,206 @@
+import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart';
+import 'package:flowy_sdk/log.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
+import 'package:flutter_bloc/flutter_bloc.dart';
+import 'package:freezed_annotation/freezed_annotation.dart';
+import 'dart:async';
+import 'filter_service.dart';
+
+part 'filter_bloc.freezed.dart';
+
+class GridFilterBloc extends Bloc<GridFilterEvent, GridFilterState> {
+  final String viewId;
+  final FilterFFIService _ffiService;
+  final FilterListener _listener;
+  GridFilterBloc({required this.viewId})
+      : _ffiService = FilterFFIService(viewId: viewId),
+        _listener = FilterListener(viewId: viewId),
+        super(GridFilterState.initial()) {
+    on<GridFilterEvent>(
+      (event, emit) async {
+        event.when(
+          initial: () async {
+            _startListening();
+            await _loadFilters();
+          },
+          deleteFilter: (
+            String fieldId,
+            String filterId,
+            FieldType fieldType,
+          ) {
+            _ffiService.deleteFilter(
+              fieldId: fieldId,
+              filterId: filterId,
+              fieldType: fieldType,
+            );
+          },
+          didReceiveFilters: (filters) {
+            emit(state.copyWith(filters: filters));
+          },
+          createCheckboxFilter: (
+            String fieldId,
+            CheckboxFilterCondition condition,
+          ) {
+            _ffiService.createCheckboxFilter(
+              fieldId: fieldId,
+              condition: condition,
+            );
+          },
+          createNumberFilter: (
+            String fieldId,
+            NumberFilterCondition condition,
+            String content,
+          ) {
+            _ffiService.createNumberFilter(
+              fieldId: fieldId,
+              condition: condition,
+              content: content,
+            );
+          },
+          createTextFilter: (
+            String fieldId,
+            TextFilterCondition condition,
+            String content,
+          ) {
+            _ffiService.createTextFilter(
+              fieldId: fieldId,
+              condition: condition,
+            );
+          },
+          createDateFilter: (
+            String fieldId,
+            DateFilterCondition condition,
+            int timestamp,
+          ) {
+            _ffiService.createDateFilter(
+              fieldId: fieldId,
+              condition: condition,
+              timestamp: timestamp,
+            );
+          },
+          createDateFilterInRange: (
+            String fieldId,
+            DateFilterCondition condition,
+            int start,
+            int end,
+          ) {
+            _ffiService.createDateFilter(
+              fieldId: fieldId,
+              condition: condition,
+              start: start,
+              end: end,
+            );
+          },
+        );
+      },
+    );
+  }
+
+  void _startListening() {
+    _listener.start(onFilterChanged: (result) {
+      result.fold(
+        (changeset) {
+          final List<FilterPB> filters = List.from(state.filters);
+
+          // Deletes the filters
+          final deleteFilterIds =
+              changeset.deleteFilters.map((e) => e.id).toList();
+          filters.retainWhere(
+            (element) => !deleteFilterIds.contains(element.id),
+          );
+
+          // Inserts the new fitler if it's not exist
+          for (final newFilter in changeset.insertFilters) {
+            final index =
+                filters.indexWhere((element) => element.id == newFilter.id);
+            if (index == -1) {
+              filters.add(newFilter);
+            }
+          }
+
+          if (!isClosed) {
+            add(GridFilterEvent.didReceiveFilters(filters));
+          }
+        },
+        (err) => Log.error(err),
+      );
+    });
+  }
+
+  Future<void> _loadFilters() async {
+    final result = await _ffiService.getAllFilters();
+    result.fold(
+      (filters) {
+        if (!isClosed) {
+          add(GridFilterEvent.didReceiveFilters(filters));
+        }
+      },
+      (err) => Log.error(err),
+    );
+  }
+
+  @override
+  Future<void> close() async {
+    await _listener.stop();
+    return super.close();
+  }
+}
+
+@freezed
+class GridFilterEvent with _$GridFilterEvent {
+  const factory GridFilterEvent.initial() = _Initial;
+  const factory GridFilterEvent.didReceiveFilters(List<FilterPB> filters) =
+      _DidReceiveFilters;
+
+  const factory GridFilterEvent.deleteFilter({
+    required String fieldId,
+    required String filterId,
+    required FieldType fieldType,
+  }) = _DeleteFilter;
+
+  const factory GridFilterEvent.createTextFilter({
+    required String fieldId,
+    required TextFilterCondition condition,
+    required String content,
+  }) = _CreateTextFilter;
+
+  const factory GridFilterEvent.createCheckboxFilter({
+    required String fieldId,
+    required CheckboxFilterCondition condition,
+  }) = _CreateCheckboxFilter;
+
+  const factory GridFilterEvent.createNumberFilter({
+    required String fieldId,
+    required NumberFilterCondition condition,
+    required String content,
+  }) = _CreateCheckboxFitler;
+
+  const factory GridFilterEvent.createDateFilter({
+    required String fieldId,
+    required DateFilterCondition condition,
+    required int start,
+  }) = _CreateDateFitler;
+
+  const factory GridFilterEvent.createDateFilterInRange({
+    required String fieldId,
+    required DateFilterCondition condition,
+    required int start,
+    required int end,
+  }) = _CreateDateFitlerInRange;
+}
+
+@freezed
+class GridFilterState with _$GridFilterState {
+  const factory GridFilterState({
+    required List<FilterPB> filters,
+  }) = _GridFilterState;
+
+  factory GridFilterState.initial() => const GridFilterState(
+        filters: [],
+      );
+}

+ 53 - 0
frontend/app_flowy/lib/plugins/grid/application/filter/filter_listener.dart

@@ -0,0 +1,53 @@
+import 'dart:typed_data';
+
+import 'package:app_flowy/core/grid_notification.dart';
+import 'package:flowy_infra/notifier.dart';
+import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart';
+import 'package:dartz/dartz.dart';
+
+typedef UpdateFilterNotifiedValue
+    = Either<FilterChangesetNotificationPB, FlowyError>;
+
+class FilterListener {
+  final String viewId;
+
+  PublishNotifier<UpdateFilterNotifiedValue>? _filterNotifier =
+      PublishNotifier();
+  GridNotificationListener? _listener;
+  FilterListener({required this.viewId});
+
+  void start({
+    required void Function(UpdateFilterNotifiedValue) onFilterChanged,
+  }) {
+    _filterNotifier?.addPublishListener(onFilterChanged);
+    _listener = GridNotificationListener(
+      objectId: viewId,
+      handler: _handler,
+    );
+  }
+
+  void _handler(
+    GridNotification ty,
+    Either<Uint8List, FlowyError> result,
+  ) {
+    switch (ty) {
+      case GridNotification.DidUpdateFilter:
+        result.fold(
+          (payload) => _filterNotifier?.value =
+              left(FilterChangesetNotificationPB.fromBuffer(payload)),
+          (error) => _filterNotifier?.value = right(error),
+        );
+        break;
+      default:
+        break;
+    }
+  }
+
+  Future<void> stop() async {
+    await _listener?.stop();
+    _filterNotifier?.dispose();
+    _filterNotifier = null;
+  }
+}

+ 48 - 1
frontend/app_flowy/lib/plugins/grid/application/filter/filter_service.dart

@@ -1,9 +1,11 @@
 import 'package:dartz/dartz.dart';
 import 'package:flowy_sdk/dispatch/dispatch.dart';
+import 'package:flowy_sdk/log.dart';
 import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
 import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
@@ -15,6 +17,17 @@ class FilterFFIService {
   final String viewId;
   const FilterFFIService({required this.viewId});
 
+  Future<Either<List<FilterPB>, FlowyError>> getAllFilters() {
+    final payload = GridIdPB()..value = viewId;
+
+    return GridEventGetAllFilters(payload).send().then((result) {
+      return result.fold(
+        (repeated) => left(repeated.items),
+        (r) => right(r),
+      );
+    });
+  }
+
   Future<Either<Unit, FlowyError>> createTextFilter({
     required String fieldId,
     required TextFilterCondition condition,
@@ -150,6 +163,40 @@ class FilterFFIService {
     final payload = GridSettingChangesetPB.create()
       ..gridId = viewId
       ..insertFilter = insertFilterPayload;
-    return GridEventUpdateGridSetting(payload).send();
+    return GridEventUpdateGridSetting(payload).send().then((result) {
+      return result.fold(
+        (l) => left(l),
+        (err) {
+          Log.error(err);
+          return right(err);
+        },
+      );
+    });
+  }
+
+  Future<Either<Unit, FlowyError>> deleteFilter({
+    required String fieldId,
+    required String filterId,
+    required FieldType fieldType,
+  }) {
+    TextFilterCondition.DoesNotContain.value;
+
+    final deleteFilterPayload = DeleteFilterPayloadPB.create()
+      ..fieldId = fieldId
+      ..filterId = filterId
+      ..fieldType = fieldType;
+
+    final payload = GridSettingChangesetPB.create()
+      ..gridId = viewId
+      ..deleteFilter = deleteFilterPayload;
+    return GridEventUpdateGridSetting(payload).send().then((result) {
+      return result.fold(
+        (l) => left(l),
+        (err) {
+          Log.error(err);
+          return right(err);
+        },
+      );
+    });
   }
 }

+ 64 - 0
frontend/app_flowy/test/bloc_test/grid_test/filter_bloc_test.dart

@@ -0,0 +1,64 @@
+import 'package:app_flowy/plugins/grid/application/filter/filter_bloc.dart';
+import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
+import 'package:flutter_test/flutter_test.dart';
+import 'package:bloc_test/bloc_test.dart';
+import 'util.dart';
+
+void main() {
+  late AppFlowyGridTest gridTest;
+  setUpAll(() async {
+    gridTest = await AppFlowyGridTest.ensureInitialized();
+  });
+
+  group('$GridFilterBloc', () {
+    setUp(() async {
+      await gridTest.createTestGrid();
+    });
+    blocTest<GridFilterBloc, GridFilterState>(
+      "create a text filter",
+      build: () => GridFilterBloc(viewId: gridTest.gridView.id)
+        ..add(const GridFilterEvent.initial()),
+      act: (bloc) async {
+        final textField = gridTest.textFieldContext();
+        bloc.add(
+          GridFilterEvent.createTextFilter(
+              fieldId: textField.id,
+              condition: TextFilterCondition.TextIsEmpty,
+              content: ""),
+        );
+      },
+      wait: const Duration(milliseconds: 300),
+      verify: (bloc) {
+        assert(bloc.state.filters.length == 1);
+      },
+    );
+
+    blocTest<GridFilterBloc, GridFilterState>(
+      "delete a text filter",
+      build: () => GridFilterBloc(viewId: gridTest.gridView.id)
+        ..add(const GridFilterEvent.initial()),
+      act: (bloc) async {
+        final textField = gridTest.textFieldContext();
+        bloc.add(
+          GridFilterEvent.createTextFilter(
+              fieldId: textField.id,
+              condition: TextFilterCondition.TextIsEmpty,
+              content: ""),
+        );
+        await gridResponseFuture();
+        final filter = bloc.state.filters.first;
+        bloc.add(
+          GridFilterEvent.deleteFilter(
+            fieldId: textField.id,
+            filterId: filter.id,
+            fieldType: textField.fieldType,
+          ),
+        );
+      },
+      wait: const Duration(milliseconds: 300),
+      verify: (bloc) {
+        assert(bloc.state.filters.isEmpty);
+      },
+    );
+  });
+}

+ 6 - 0
frontend/app_flowy/test/bloc_test/grid_test/util.dart

@@ -157,6 +157,12 @@ class AppFlowyGridTest {
     return GridFieldCellContext(gridId: gridView.id, field: field);
   }
 
+  GridFieldContext textFieldContext() {
+    final fieldContext = fieldContexts
+        .firstWhere((element) => element.fieldType == FieldType.RichText);
+    return fieldContext;
+  }
+
   Future<void> createTestGrid() async {
     final app = await unitTest.createTestApp();
     final builder = GridPluginBuilder();

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

@@ -14,6 +14,7 @@ pub enum GridNotification {
     DidUpdateGroupView = 60,
     DidUpdateGroup = 61,
     DidGroupByNewField = 62,
+    DidUpdateFilter = 63,
     DidUpdateGridSetting = 70,
 }
 

+ 2 - 2
frontend/rust-lib/flowy-grid/src/entities/filter_entities/checkbox_filter.rs

@@ -40,8 +40,8 @@ impl std::convert::TryFrom<u8> for CheckboxFilterCondition {
     }
 }
 
-impl std::convert::From<Arc<FilterRevision>> for CheckboxFilterPB {
-    fn from(rev: Arc<FilterRevision>) -> Self {
+impl std::convert::From<&FilterRevision> for CheckboxFilterPB {
+    fn from(rev: &FilterRevision) -> Self {
         CheckboxFilterPB {
             condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked),
         }

+ 2 - 2
frontend/rust-lib/flowy-grid/src/entities/filter_entities/date_filter.rs

@@ -81,8 +81,8 @@ impl std::convert::TryFrom<u8> for DateFilterCondition {
         }
     }
 }
-impl std::convert::From<Arc<FilterRevision>> for DateFilterPB {
-    fn from(rev: Arc<FilterRevision>) -> Self {
+impl std::convert::From<&FilterRevision> for DateFilterPB {
+    fn from(rev: &FilterRevision) -> Self {
         let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs);
         let mut filter = DateFilterPB {
             condition,

+ 31 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/filter_changeset.rs

@@ -0,0 +1,31 @@
+use crate::entities::{FilterPB, InsertedRowPB, RepeatedFilterPB, RowPB};
+use flowy_derive::ProtoBuf;
+
+#[derive(Debug, Default, ProtoBuf)]
+pub struct FilterChangesetNotificationPB {
+    #[pb(index = 1)]
+    pub view_id: String,
+
+    #[pb(index = 2)]
+    pub insert_filters: Vec<FilterPB>,
+
+    #[pb(index = 3)]
+    pub delete_filters: Vec<FilterPB>,
+}
+
+impl FilterChangesetNotificationPB {
+    pub fn from_insert(view_id: &str, filters: Vec<FilterPB>) -> Self {
+        Self {
+            view_id: view_id.to_string(),
+            insert_filters: filters,
+            delete_filters: Default::default(),
+        }
+    }
+    pub fn from_delete(view_id: &str, filters: Vec<FilterPB>) -> Self {
+        Self {
+            view_id: view_id.to_string(),
+            insert_filters: Default::default(),
+            delete_filters: filters,
+        }
+    }
+}

+ 2 - 0
frontend/rust-lib/flowy-grid/src/entities/filter_entities/mod.rs

@@ -1,5 +1,6 @@
 mod checkbox_filter;
 mod date_filter;
+mod filter_changeset;
 mod number_filter;
 mod select_option_filter;
 mod text_filter;
@@ -7,6 +8,7 @@ mod util;
 
 pub use checkbox_filter::*;
 pub use date_filter::*;
+pub use filter_changeset::*;
 pub use number_filter::*;
 pub use select_option_filter::*;
 pub use text_filter::*;

+ 2 - 2
frontend/rust-lib/flowy-grid/src/entities/filter_entities/number_filter.rs

@@ -55,8 +55,8 @@ impl std::convert::TryFrom<u8> for NumberFilterCondition {
     }
 }
 
-impl std::convert::From<Arc<FilterRevision>> for NumberFilterPB {
-    fn from(rev: Arc<FilterRevision>) -> Self {
+impl std::convert::From<&FilterRevision> for NumberFilterPB {
+    fn from(rev: &FilterRevision) -> Self {
         NumberFilterPB {
             condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
             content: rev.content.clone(),

+ 2 - 2
frontend/rust-lib/flowy-grid/src/entities/filter_entities/select_option_filter.rs

@@ -48,8 +48,8 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
     }
 }
 
-impl std::convert::From<Arc<FilterRevision>> for SelectOptionFilterPB {
-    fn from(rev: Arc<FilterRevision>) -> Self {
+impl std::convert::From<&FilterRevision> for SelectOptionFilterPB {
+    fn from(rev: &FilterRevision) -> Self {
         let ids = SelectOptionIds::from(rev.content.clone());
         SelectOptionFilterPB {
             condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),

+ 2 - 2
frontend/rust-lib/flowy-grid/src/entities/filter_entities/text_filter.rs

@@ -55,8 +55,8 @@ impl std::convert::TryFrom<u8> for TextFilterCondition {
     }
 }
 
-impl std::convert::From<Arc<FilterRevision>> for TextFilterPB {
-    fn from(rev: Arc<FilterRevision>) -> Self {
+impl std::convert::From<&FilterRevision> for TextFilterPB {
+    fn from(rev: &FilterRevision) -> Self {
         TextFilterPB {
             condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
             content: rev.content.clone(),

+ 29 - 9
frontend/rust-lib/flowy-grid/src/entities/filter_entities/util.rs

@@ -15,29 +15,49 @@ use std::sync::Arc;
 pub struct FilterPB {
     #[pb(index = 1)]
     pub id: String,
-}
 
-#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
-pub struct RepeatedGridFilterConfigurationPB {
-    #[pb(index = 1)]
-    pub items: Vec<FilterPB>,
+    #[pb(index = 2)]
+    pub ty: FieldType,
+
+    #[pb(index = 3)]
+    pub data: Vec<u8>,
 }
 
 impl std::convert::From<&FilterRevision> for FilterPB {
     fn from(rev: &FilterRevision) -> Self {
-        Self { id: rev.id.clone() }
+        let field_type: FieldType = rev.field_type_rev.into();
+        let bytes: Bytes = match field_type {
+            FieldType::RichText => TextFilterPB::from(rev).try_into().unwrap(),
+            FieldType::Number => NumberFilterPB::from(rev).try_into().unwrap(),
+            FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(),
+            FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
+            FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
+            FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(),
+            FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(),
+        };
+        Self {
+            id: rev.id.clone(),
+            ty: rev.field_type_rev.into(),
+            data: bytes.to_vec(),
+        }
     }
 }
 
-impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedGridFilterConfigurationPB {
+#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
+pub struct RepeatedFilterPB {
+    #[pb(index = 1)]
+    pub items: Vec<FilterPB>,
+}
+
+impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedFilterPB {
     fn from(revs: Vec<Arc<FilterRevision>>) -> Self {
-        RepeatedGridFilterConfigurationPB {
+        RepeatedFilterPB {
             items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
         }
     }
 }
 
-impl std::convert::From<Vec<FilterPB>> for RepeatedGridFilterConfigurationPB {
+impl std::convert::From<Vec<FilterPB>> for RepeatedFilterPB {
     fn from(items: Vec<FilterPB>) -> Self {
         Self { items }
     }

+ 3 - 3
frontend/rust-lib/flowy-grid/src/entities/group_entities/group_changeset.rs

@@ -5,7 +5,7 @@ use flowy_error::ErrorCode;
 use std::fmt::Formatter;
 
 #[derive(Debug, Default, ProtoBuf)]
-pub struct GroupChangesetPB {
+pub struct GroupRowsNotificationPB {
     #[pb(index = 1)]
     pub group_id: String,
 
@@ -22,7 +22,7 @@ pub struct GroupChangesetPB {
     pub updated_rows: Vec<RowPB>,
 }
 
-impl std::fmt::Display for GroupChangesetPB {
+impl std::fmt::Display for GroupRowsNotificationPB {
     fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
         for inserted_row in &self.inserted_rows {
             let _ = f.write_fmt(format_args!(
@@ -39,7 +39,7 @@ impl std::fmt::Display for GroupChangesetPB {
     }
 }
 
-impl GroupChangesetPB {
+impl GroupRowsNotificationPB {
     pub fn is_empty(&self) -> bool {
         self.group_name.is_none()
             && self.inserted_rows.is_empty()

+ 2 - 3
frontend/rust-lib/flowy-grid/src/entities/setting_entities.rs

@@ -1,8 +1,7 @@
 use crate::entities::parser::NotEmptyStr;
 use crate::entities::{
     CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
-    DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB,
-    RepeatedGridGroupConfigurationPB,
+    DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGridGroupConfigurationPB,
 };
 use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
 use flowy_error::ErrorCode;
@@ -21,7 +20,7 @@ pub struct GridSettingPB {
     pub layout_type: GridLayout,
 
     #[pb(index = 3)]
-    pub filter_configurations: RepeatedGridFilterConfigurationPB,
+    pub filter_configurations: RepeatedFilterPB,
 
     #[pb(index = 4)]
     pub group_configurations: RepeatedGridGroupConfigurationPB,

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

@@ -61,6 +61,19 @@ pub(crate) async fn update_grid_setting_handler(
     Ok(())
 }
 
+#[tracing::instrument(level = "trace", skip(data, manager), err)]
+pub(crate) async fn get_all_filters_handler(
+    data: Data<GridIdPB>,
+    manager: AppData<Arc<GridManager>>,
+) -> DataResult<RepeatedFilterPB, FlowyError> {
+    let grid_id: GridIdPB = data.into_inner();
+    let editor = manager.open_grid(grid_id).await?;
+    let filters = RepeatedFilterPB {
+        items: editor.get_all_filters().await?,
+    };
+    data_result(filters)
+}
+
 #[tracing::instrument(level = "debug", skip(data, manager), err)]
 pub(crate) async fn get_grid_blocks_handler(
     data: Data<QueryBlocksPayloadPB>,

+ 4 - 0
frontend/rust-lib/flowy-grid/src/event_map.rs

@@ -12,6 +12,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
         .event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
         .event(GridEvent::GetGridSetting, get_grid_setting_handler)
         .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
+        .event(GridEvent::GetAllFilters, get_all_filters_handler)
         // Field
         .event(GridEvent::GetFields, get_fields_handler)
         .event(GridEvent::UpdateField, update_field_handler)
@@ -78,6 +79,9 @@ pub enum GridEvent {
     #[event(input = "GridSettingChangesetPB")]
     UpdateGridSetting = 3,
 
+    #[event(input = "GridIdPB", output = "RepeatedFilterPB")]
+    GetAllFilters = 4,
+
     /// [GetFields] event is used to get the grid's settings.
     ///
     /// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB]

+ 6 - 6
frontend/rust-lib/flowy-grid/src/services/filter/controller.rs

@@ -181,37 +181,37 @@ impl FilterController {
                         let _ = self
                             .filter_map
                             .text_filter
-                            .insert(filter_type, TextFilterPB::from(filter_rev));
+                            .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
                     }
                     FieldType::Number => {
                         let _ = self
                             .filter_map
                             .number_filter
-                            .insert(filter_type, NumberFilterPB::from(filter_rev));
+                            .insert(filter_type, NumberFilterPB::from(filter_rev.as_ref()));
                     }
                     FieldType::DateTime => {
                         let _ = self
                             .filter_map
                             .date_filter
-                            .insert(filter_type, DateFilterPB::from(filter_rev));
+                            .insert(filter_type, DateFilterPB::from(filter_rev.as_ref()));
                     }
                     FieldType::SingleSelect | FieldType::MultiSelect => {
                         let _ = self
                             .filter_map
                             .select_option_filter
-                            .insert(filter_type, SelectOptionFilterPB::from(filter_rev));
+                            .insert(filter_type, SelectOptionFilterPB::from(filter_rev.as_ref()));
                     }
                     FieldType::Checkbox => {
                         let _ = self
                             .filter_map
                             .checkbox_filter
-                            .insert(filter_type, CheckboxFilterPB::from(filter_rev));
+                            .insert(filter_type, CheckboxFilterPB::from(filter_rev.as_ref()));
                     }
                     FieldType::URL => {
                         let _ = self
                             .filter_map
                             .url_filter
-                            .insert(filter_type, TextFilterPB::from(filter_rev));
+                            .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
                     }
                 }
             }

+ 38 - 16
frontend/rust-lib/flowy-grid/src/services/grid_view_editor.rs

@@ -132,8 +132,8 @@ impl GridViewRevisionEditor {
                     index,
                     is_new: true,
                 };
-                let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]);
-                self.notify_did_update_group(changeset).await;
+                let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]);
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
@@ -150,7 +150,7 @@ impl GridViewRevisionEditor {
         tracing::trace!("Delete row in view changeset: {:?}", changesets);
         if let Some(changesets) = changesets {
             for changeset in changesets {
-                self.notify_did_update_group(changeset).await;
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
@@ -164,7 +164,7 @@ impl GridViewRevisionEditor {
 
         if let Some(changesets) = changesets {
             for changeset in changesets {
-                self.notify_did_update_group(changeset).await;
+                self.notify_did_update_group_rows(changeset).await;
             }
         }
     }
@@ -175,7 +175,7 @@ impl GridViewRevisionEditor {
         row_changeset: &mut RowChangeset,
         to_group_id: &str,
         to_row_id: Option<String>,
-    ) -> Vec<GroupChangesetPB> {
+    ) -> Vec<GroupRowsNotificationPB> {
         let changesets = self
             .mut_group_controller(|group_controller, field_rev| {
                 let move_row_context = MoveGroupRowContext {
@@ -252,9 +252,12 @@ impl GridViewRevisionEditor {
         self.pad.read().await.get_all_filters(&field_revs)
     }
 
-    pub(crate) async fn get_view_filters(&self, filter_id: &FilterType) -> Vec<Arc<FilterRevision>> {
-        let field_type_rev: FieldTypeRevision = filter_id.field_type.clone().into();
-        self.pad.read().await.get_filters(&filter_id.field_id, &field_type_rev)
+    pub(crate) async fn get_view_filters(&self, filter_type: &FilterType) -> Vec<Arc<FilterRevision>> {
+        let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into();
+        self.pad
+            .read()
+            .await
+            .get_filters(&filter_type.field_id, &field_type_rev)
     }
 
     /// Initialize new group when grouping by a new field
@@ -290,14 +293,16 @@ impl GridViewRevisionEditor {
 
     pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
         let filter_type = FilterType::from(&params);
+        let filter_rev = FilterRevision {
+            id: gen_grid_filter_id(),
+            field_id: params.field_id.clone(),
+            field_type_rev: params.field_type_rev,
+            condition: params.condition,
+            content: params.content,
+        };
+        let filter_pb = FilterPB::from(&filter_rev);
         let _ = self
             .modify(|pad| {
-                let filter_rev = FilterRevision {
-                    id: gen_grid_filter_id(),
-                    field_id: params.field_id.clone(),
-                    condition: params.condition,
-                    content: params.content,
-                };
                 let changeset = pad.insert_filter(&params.field_id, &params.field_type_rev, filter_rev)?;
                 Ok(changeset)
             })
@@ -309,12 +314,20 @@ impl GridViewRevisionEditor {
             .apply_changeset(FilterChangeset::from_insert(filter_type))
             .await;
 
+        let changeset = FilterChangesetNotificationPB::from_insert(&self.view_id, vec![filter_pb]);
+        self.notify_did_update_filter(changeset).await;
         Ok(())
     }
 
     pub(crate) async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
         let filter_type = params.filter_type;
         let field_type_rev = filter_type.field_type_rev();
+        let filters = self
+            .get_view_filters(&filter_type)
+            .await
+            .into_iter()
+            .map(|filter| FilterPB::from(filter.as_ref()))
+            .collect();
         let _ = self
             .modify(|pad| {
                 let changeset = pad.delete_filter(&params.filter_id, &filter_type.field_id, &field_type_rev)?;
@@ -327,6 +340,9 @@ impl GridViewRevisionEditor {
             .await
             .apply_changeset(FilterChangeset::from_delete(filter_type))
             .await;
+
+        let changeset = FilterChangesetNotificationPB::from_delete(&self.view_id, filters);
+        self.notify_did_update_filter(changeset).await;
         Ok(())
     }
 
@@ -394,8 +410,14 @@ impl GridViewRevisionEditor {
             .send();
     }
 
-    pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) {
-        send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup)
+    pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) {
+        send_dart_notification(&payload.group_id, GridNotification::DidUpdateGroup)
+            .payload(payload)
+            .send();
+    }
+
+    pub async fn notify_did_update_filter(&self, changeset: FilterChangesetNotificationPB) {
+        send_dart_notification(&changeset.view_id, GridNotification::DidUpdateFilter)
             .payload(changeset)
             .send();
     }

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

@@ -168,7 +168,7 @@ impl GridViewManager {
         }
 
         for group_changeset in group_changesets {
-            view_editor.notify_did_update_group(group_changeset).await;
+            view_editor.notify_did_update_group_rows(group_changeset).await;
         }
 
         Ok(())

+ 11 - 7
frontend/rust-lib/flowy-grid/src/services/group/action.rs

@@ -1,4 +1,4 @@
-use crate::entities::{GroupChangesetPB, GroupViewChangesetPB};
+use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB};
 use crate::services::cell::CellDataIsEmpty;
 use crate::services::group::controller::MoveGroupRowContext;
 use crate::services::group::Group;
@@ -31,13 +31,17 @@ pub trait GroupControllerCustomActions: Send + Sync {
         &mut self,
         row_rev: &RowRevision,
         cell_data: &Self::CellDataType,
-    ) -> Vec<GroupChangesetPB>;
+    ) -> Vec<GroupRowsNotificationPB>;
 
     /// Deletes the row from the group
-    fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>;
+    fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB>;
 
     /// Move row from one group to another
-    fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupChangesetPB>;
+    fn move_row(
+        &mut self,
+        cell_data: &Self::CellDataType,
+        context: MoveGroupRowContext,
+    ) -> Vec<GroupRowsNotificationPB>;
 }
 
 /// Defines the shared actions any group controller can perform.
@@ -62,17 +66,17 @@ pub trait GroupControllerSharedActions: Send + Sync {
         &mut self,
         row_rev: &RowRevision,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupChangesetPB>>;
+    ) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
 
     /// Remove the row from the group if the row gets deleted
     fn did_delete_delete_row(
         &mut self,
         row_rev: &RowRevision,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupChangesetPB>>;
+    ) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
 
     /// Move the row from one group to another group
-    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>;
+    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
 
     /// Update the group if the corresponding field is changed
     fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;

+ 8 - 8
frontend/rust-lib/flowy-grid/src/services/group/controller.rs

@@ -1,4 +1,4 @@
-use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
+use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
 use crate::services::cell::{decode_any_cell_data, CellBytesParser, CellDataIsEmpty};
 use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
 use crate::services::group::configuration::GroupContext;
@@ -89,8 +89,8 @@ where
     fn update_default_group(
         &mut self,
         row_rev: &RowRevision,
-        other_group_changesets: &[GroupChangesetPB],
-    ) -> Option<GroupChangesetPB> {
+        other_group_changesets: &[GroupRowsNotificationPB],
+    ) -> Option<GroupRowsNotificationPB> {
         let default_group = self.group_ctx.get_mut_no_status_group()?;
 
         // [other_group_inserted_row] contains all the inserted rows except the default group.
@@ -113,7 +113,7 @@ where
             })
             .collect::<Vec<String>>();
 
-        let mut changeset = GroupChangesetPB::new(default_group.id.clone());
+        let mut changeset = GroupRowsNotificationPB::new(default_group.id.clone());
         if !default_group_inserted_row.is_empty() {
             changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into()));
             default_group.add_row(row_rev.into());
@@ -222,7 +222,7 @@ where
         &mut self,
         row_rev: &RowRevision,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupChangesetPB>> {
+    ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
             let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
             let cell_data = cell_bytes.parser::<P>()?;
@@ -244,7 +244,7 @@ where
         &mut self,
         row_rev: &RowRevision,
         field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupChangesetPB>> {
+    ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         // if the cell_rev is none, then the row must in the default group.
         if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
             let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
@@ -264,7 +264,7 @@ where
                 if !no_status_group.contains_row(&row_rev.id) {
                     tracing::error!("The row: {} should be in the no status group", row_rev.id);
                 }
-                Ok(vec![GroupChangesetPB::delete(
+                Ok(vec![GroupRowsNotificationPB::delete(
                     no_status_group.id.clone(),
                     vec![row_rev.id.clone()],
                 )])
@@ -273,7 +273,7 @@ where
     }
 
     #[tracing::instrument(level = "trace", skip_all, err)]
-    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> {
+    fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         let cell_rev = match context.row_rev.cells.get(&self.field_id) {
             Some(cell_rev) => Some(cell_rev.clone()),
             None => self.default_cell_rev(),

+ 10 - 6
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/checkbox_controller.rs

@@ -1,4 +1,4 @@
-use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB};
+use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
 use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
 use crate::services::group::action::GroupControllerCustomActions;
 use crate::services::group::configuration::GroupContext;
@@ -37,10 +37,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
         &mut self,
         row_rev: &RowRevision,
         cell_data: &Self::CellDataType,
-    ) -> Vec<GroupChangesetPB> {
+    ) -> Vec<GroupRowsNotificationPB> {
         let mut changesets = vec![];
         self.group_ctx.iter_mut_status_groups(|group| {
-            let mut changeset = GroupChangesetPB::new(group.id.clone());
+            let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
             let is_not_contained = !group.contains_row(&row_rev.id);
             if group.id == CHECK {
                 if cell_data.is_uncheck() {
@@ -79,10 +79,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
         changesets
     }
 
-    fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
+    fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
         let mut changesets = vec![];
         self.group_ctx.iter_mut_groups(|group| {
-            let mut changeset = GroupChangesetPB::new(group.id.clone());
+            let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
             if group.contains_row(&row_rev.id) {
                 changeset.deleted_rows.push(row_rev.id.clone());
                 group.remove_row(&row_rev.id);
@@ -95,7 +95,11 @@ impl GroupControllerCustomActions for CheckboxGroupController {
         changesets
     }
 
-    fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
+    fn move_row(
+        &mut self,
+        _cell_data: &Self::CellDataType,
+        mut context: MoveGroupRowContext,
+    ) -> Vec<GroupRowsNotificationPB> {
         let mut group_changeset = vec![];
         self.group_ctx.iter_mut_groups(|group| {
             if let Some(changeset) = move_group_row(group, &mut context) {

+ 4 - 4
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/default_controller.rs

@@ -1,4 +1,4 @@
-use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB};
+use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, RowPB};
 use crate::services::group::action::GroupControllerSharedActions;
 use crate::services::group::{Group, GroupController, MoveGroupRowContext};
 use flowy_error::FlowyResult;
@@ -59,7 +59,7 @@ impl GroupControllerSharedActions for DefaultGroupController {
         &mut self,
         _row_rev: &RowRevision,
         _field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupChangesetPB>> {
+    ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         Ok(vec![])
     }
 
@@ -67,11 +67,11 @@ impl GroupControllerSharedActions for DefaultGroupController {
         &mut self,
         _row_rev: &RowRevision,
         _field_rev: &FieldRevision,
-    ) -> FlowyResult<Vec<GroupChangesetPB>> {
+    ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         Ok(vec![])
     }
 
-    fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> {
+    fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
         todo!()
     }
 

+ 8 - 4
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/multi_select_controller.rs

@@ -1,4 +1,4 @@
-use crate::entities::{GroupChangesetPB, RowPB};
+use crate::entities::{GroupRowsNotificationPB, RowPB};
 use crate::services::cell::insert_select_option_cell;
 use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
 use crate::services::group::action::GroupControllerCustomActions;
@@ -30,7 +30,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
         &mut self,
         row_rev: &RowRevision,
         cell_data: &Self::CellDataType,
-    ) -> Vec<GroupChangesetPB> {
+    ) -> Vec<GroupRowsNotificationPB> {
         let mut changesets = vec![];
         self.group_ctx.iter_mut_status_groups(|group| {
             if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
@@ -40,7 +40,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
         changesets
     }
 
-    fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
+    fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
         let mut changesets = vec![];
         self.group_ctx.iter_mut_status_groups(|group| {
             if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
@@ -50,7 +50,11 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
         changesets
     }
 
-    fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
+    fn move_row(
+        &mut self,
+        _cell_data: &Self::CellDataType,
+        mut context: MoveGroupRowContext,
+    ) -> Vec<GroupRowsNotificationPB> {
         let mut group_changeset = vec![];
         self.group_ctx.iter_mut_groups(|group| {
             if let Some(changeset) = move_group_row(group, &mut context) {

+ 8 - 4
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/single_select_controller.rs

@@ -1,4 +1,4 @@
-use crate::entities::{GroupChangesetPB, RowPB};
+use crate::entities::{GroupRowsNotificationPB, RowPB};
 use crate::services::cell::insert_select_option_cell;
 use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
 use crate::services::group::action::GroupControllerCustomActions;
@@ -30,7 +30,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
         &mut self,
         row_rev: &RowRevision,
         cell_data: &Self::CellDataType,
-    ) -> Vec<GroupChangesetPB> {
+    ) -> Vec<GroupRowsNotificationPB> {
         let mut changesets = vec![];
         self.group_ctx.iter_mut_status_groups(|group| {
             if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
@@ -40,7 +40,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
         changesets
     }
 
-    fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> {
+    fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
         let mut changesets = vec![];
         self.group_ctx.iter_mut_status_groups(|group| {
             if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
@@ -50,7 +50,11 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
         changesets
     }
 
-    fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> {
+    fn move_row(
+        &mut self,
+        _cell_data: &Self::CellDataType,
+        mut context: MoveGroupRowContext,
+    ) -> Vec<GroupRowsNotificationPB> {
         let mut group_changeset = vec![];
         self.group_ctx.iter_mut_groups(|group| {
             if let Some(changeset) = move_group_row(group, &mut context) {

+ 7 - 7
frontend/rust-lib/flowy-grid/src/services/group/controller_impls/select_option_controller/util.rs

@@ -1,4 +1,4 @@
-use crate::entities::{FieldType, GroupChangesetPB, InsertedRowPB, RowPB};
+use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowPB};
 use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell};
 use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK};
 use crate::services::group::configuration::GroupContext;
@@ -12,8 +12,8 @@ pub fn add_or_remove_select_option_row(
     group: &mut Group,
     cell_data: &SelectOptionCellDataPB,
     row_rev: &RowRevision,
-) -> Option<GroupChangesetPB> {
-    let mut changeset = GroupChangesetPB::new(group.id.clone());
+) -> Option<GroupRowsNotificationPB> {
+    let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
     if cell_data.select_options.is_empty() {
         if group.contains_row(&row_rev.id) {
             changeset.deleted_rows.push(row_rev.id.clone());
@@ -45,8 +45,8 @@ pub fn remove_select_option_row(
     group: &mut Group,
     cell_data: &SelectOptionCellDataPB,
     row_rev: &RowRevision,
-) -> Option<GroupChangesetPB> {
-    let mut changeset = GroupChangesetPB::new(group.id.clone());
+) -> Option<GroupRowsNotificationPB> {
+    let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
     cell_data.select_options.iter().for_each(|option| {
         if option.id == group.id && group.contains_row(&row_rev.id) {
             changeset.deleted_rows.push(row_rev.id.clone());
@@ -61,8 +61,8 @@ pub fn remove_select_option_row(
     }
 }
 
-pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option<GroupChangesetPB> {
-    let mut changeset = GroupChangesetPB::new(group.id.clone());
+pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option<GroupRowsNotificationPB> {
+    let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
     let MoveGroupRowContext {
         row_rev,
         row_changeset,

+ 2 - 0
shared-lib/grid-rev-model/src/filter_rev.rs

@@ -1,9 +1,11 @@
+use crate::FieldTypeRevision;
 use serde::{Deserialize, Serialize};
 
 #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
 pub struct FilterRevision {
     pub id: String,
     pub field_id: String,
+    pub field_type_rev: FieldTypeRevision,
     pub condition: u8,
     #[serde(default)]
     pub content: String,