Kaynağa Gözat

feat: selectOption sort + fix logic in edge cases (#3109)

Richard Shiue 1 yıl önce
ebeveyn
işleme
58ecf62240
37 değiştirilmiş dosya ile 522 ekleme ve 307 silme
  1. 2 0
      frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart
  2. 1 1
      frontend/rust-lib/flowy-core/src/module.rs
  3. 2 3
      frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs
  4. 28 11
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs
  5. 7 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs
  6. 5 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs
  7. 7 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs
  8. 2 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs
  9. 10 9
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs
  10. 7 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs
  11. 35 14
      frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs
  12. 39 27
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs
  13. 7 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_ids.rs
  14. 3 2
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs
  15. 12 11
      frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs
  16. 19 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs
  17. 35 4
      frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs
  18. 55 26
      frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs
  19. 15 7
      frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs
  20. 7 1
      frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option_entities.rs
  21. 9 34
      frontend/rust-lib/flowy-database2/src/services/sort/controller.rs
  22. 12 0
      frontend/rust-lib/flowy-database2/src/services/sort/entities.rs
  23. 1 1
      frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs
  24. 1 1
      frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs
  25. 1 1
      frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs
  26. 5 5
      frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs
  27. 1 1
      frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs
  28. 12 5
      frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs
  29. 4 3
      frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs
  30. 0 49
      frontend/rust-lib/flowy-database2/tests/database/sort_test/checkbox_and_text_test.rs
  31. 0 1
      frontend/rust-lib/flowy-database2/tests/database/sort_test/mod.rs
  32. 21 23
      frontend/rust-lib/flowy-database2/tests/database/sort_test/multi_sort_test.rs
  33. 145 36
      frontend/rust-lib/flowy-database2/tests/database/sort_test/single_sort_test.rs
  34. 7 7
      frontend/rust-lib/flowy-folder2/src/entities/icon.rs
  35. 2 2
      frontend/rust-lib/flowy-test/src/lib.rs
  36. 2 2
      frontend/rust-lib/flowy-test/tests/folder/local_test/subscription_test.rs
  37. 1 1
      frontend/rust-lib/flowy-user/src/event_map.rs

+ 2 - 0
frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart

@@ -791,6 +791,8 @@ class FieldInfo {
       case FieldType.Checkbox:
       case FieldType.Number:
       case FieldType.DateTime:
+      case FieldType.SingleSelect:
+      case FieldType.MultiSelect:
         return true;
       default:
         return false;

+ 1 - 1
frontend/rust-lib/flowy-core/src/module.rs

@@ -14,7 +14,7 @@ pub fn make_plugins(
 ) -> Vec<AFPlugin> {
   let store_preferences = user_session
     .upgrade()
-    .and_then(|session| Some(session.get_store_preferences()))
+    .map(|session| session.get_store_preferences())
     .unwrap();
   let user_plugin = flowy_user::event_map::init(user_session);
   let folder_plugin = flowy_folder2::event_map::init(folder_manager);

+ 2 - 3
frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs

@@ -31,13 +31,12 @@ impl DatabaseLayoutDepsResolver {
     match self.database_layout {
       DatabaseLayout::Grid => (None, None),
       DatabaseLayout::Board => {
-        if self
+        if !self
           .database
           .lock()
           .get_fields(None)
           .into_iter()
-          .find(|field| FieldType::from(field.field_type).can_be_group())
-          .is_none()
+          .any(|field| FieldType::from(field.field_type).can_be_group())
         {
           let select_field = self.create_select_field();
           (Some(select_field), None)

+ 28 - 11
frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs

@@ -11,9 +11,10 @@ use flowy_error::FlowyResult;
 use crate::entities::{CheckboxFilterPB, FieldType};
 use crate::services::cell::{CellDataChangeset, CellDataDecoder};
 use crate::services::field::{
-  default_order, CheckboxCellData, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter, TypeOptionTransform,
+  CheckboxCellData, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOptionCellDataSerde, TypeOptionTransform,
 };
+use crate::services::sort::SortCondition;
 
 #[derive(Debug, Clone, Serialize, Deserialize, Default)]
 pub struct CheckboxTypeOption {
@@ -68,7 +69,7 @@ impl From<CheckboxTypeOption> for TypeOptionData {
   }
 }
 
-impl TypeOptionCellData for CheckboxTypeOption {
+impl TypeOptionCellDataSerde for CheckboxTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -135,16 +136,32 @@ impl TypeOptionCellDataCompare for CheckboxTypeOption {
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
-    match (cell_data.is_check(), other_cell_data.is_check()) {
-      (true, true) => Ordering::Equal,
-      (true, false) => Ordering::Greater,
-      (false, true) => Ordering::Less,
-      (false, false) => default_order(),
-    }
+    let order = cell_data.is_check().cmp(&other_cell_data.is_check());
+    sort_condition.evaluate_order(order)
   }
 
-  fn exempt_from_cmp(&self, _: &<Self as TypeOption>::CellData) -> bool {
-    false
+  /// Compares two cell data using a specified sort condition and accounts for uninitialized cells.
+  ///
+  /// This function checks if either `cell_data` or `other_cell_data` is checked (i.e., has the `is_check` property set).
+  /// If the right cell is checked and the left cell isn't, the function will return `Ordering::Less`. Conversely, if the
+  /// left cell is checked and the right one isn't, the function will return `Ordering::Greater`. In all other cases, it returns
+  /// `Ordering::Equal`.
+  fn apply_cmp_with_uninitialized(
+    &self,
+    cell_data: Option<&<Self as TypeOption>::CellData>,
+    other_cell_data: Option<&<Self as TypeOption>::CellData>,
+    sort_condition: SortCondition,
+  ) -> Ordering {
+    match (cell_data, other_cell_data) {
+      (None, Some(right_cell_data)) if right_cell_data.is_check() => {
+        sort_condition.evaluate_order(Ordering::Less)
+      },
+      (Some(left_cell_data), None) if left_cell_data.is_check() => {
+        sort_condition.evaluate_order(Ordering::Greater)
+      },
+      _ => Ordering::Equal,
+    }
   }
 }

+ 7 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs

@@ -9,7 +9,7 @@ use flowy_error::{FlowyError, FlowyResult};
 
 use crate::entities::FieldType;
 use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString};
-use crate::services::field::CELL_DATA;
+use crate::services::field::{TypeOptionCellData, CELL_DATA};
 
 pub const CHECK: &str = "Yes";
 pub const UNCHECK: &str = "No";
@@ -31,6 +31,12 @@ impl CheckboxCellData {
   }
 }
 
+impl TypeOptionCellData for CheckboxCellData {
+  fn is_cell_empty(&self) -> bool {
+    false
+  }
+}
+
 impl AsRef<[u8]> for CheckboxCellData {
   fn as_ref(&self) -> &[u8] {
     self.0.as_ref()

+ 5 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs

@@ -2,9 +2,10 @@ use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectO
 use crate::services::cell::{CellDataChangeset, CellDataDecoder};
 use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
 use crate::services::field::{
-  SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
+  SelectOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
 };
+use crate::services::sort::SortCondition;
 use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
 use collab_database::rows::Cell;
 use flowy_error::FlowyResult;
@@ -32,7 +33,7 @@ impl From<ChecklistTypeOption> for TypeOptionData {
   }
 }
 
-impl TypeOptionCellData for ChecklistTypeOption {
+impl TypeOptionCellDataSerde for ChecklistTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -191,6 +192,7 @@ impl TypeOptionCellDataCompare for ChecklistTypeOption {
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    _sort_condition: SortCondition,
   ) -> Ordering {
     let left = cell_data.percentage_complete();
     let right = other_cell_data.percentage_complete();
@@ -202,10 +204,6 @@ impl TypeOptionCellDataCompare for ChecklistTypeOption {
       Ordering::Equal
     }
   }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.selected_option_ids.is_empty()
-  }
 }
 
 impl TypeOptionTransform for ChecklistTypeOption {}

+ 7 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs

@@ -1,6 +1,6 @@
 use crate::entities::FieldType;
 use crate::services::cell::{FromCellChangeset, ToCellChangeset};
-use crate::services::field::{SelectOption, CELL_DATA};
+use crate::services::field::{SelectOption, TypeOptionCellData, CELL_DATA};
 use collab::core::any_map::AnyMapExtension;
 use collab_database::rows::{new_cell_builder, Cell};
 use flowy_error::{internal_error, FlowyResult};
@@ -19,6 +19,12 @@ impl ToString for ChecklistCellData {
   }
 }
 
+impl TypeOptionCellData for ChecklistCellData {
+  fn is_cell_empty(&self) -> bool {
+    self.selected_option_ids.is_empty()
+  }
+}
+
 impl ChecklistCellData {
   pub fn selected_options(&self) -> Vec<SelectOption> {
     self

+ 2 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs

@@ -9,7 +9,8 @@ mod tests {
   use crate::entities::FieldType;
   use crate::services::cell::{CellDataChangeset, CellDataDecoder};
   use crate::services::field::{
-    DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder, TimeFormat, TypeOptionCellData,
+    DateCellChangeset, DateFormat, DateTypeOption, FieldBuilder, TimeFormat,
+    TypeOptionCellDataSerde,
   };
 
   #[test]

+ 10 - 9
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs

@@ -2,9 +2,10 @@ use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
 use crate::services::cell::{CellDataChangeset, CellDataDecoder};
 use crate::services::field::{
   default_order, DateCellChangeset, DateCellData, DateCellDataWrapper, DateFormat, TimeFormat,
-  TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
   TypeOptionTransform,
 };
+use crate::services::sort::SortCondition;
 use chrono::format::strftime::StrftimeItems;
 use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
 use chrono_tz::Tz;
@@ -80,7 +81,7 @@ impl From<DateTypeOption> for TypeOptionData {
   }
 }
 
-impl TypeOptionCellData for DateTypeOption {
+impl TypeOptionCellDataSerde for DateTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -306,16 +307,16 @@ impl TypeOptionCellDataCompare for DateTypeOption {
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
     match (cell_data.timestamp, other_cell_data.timestamp) {
-      (Some(left), Some(right)) => left.cmp(&right),
-      (Some(_), None) => Ordering::Greater,
-      (None, Some(_)) => Ordering::Less,
+      (Some(left), Some(right)) => {
+        let order = left.cmp(&right);
+        sort_condition.evaluate_order(order)
+      },
+      (Some(_), None) => Ordering::Less,
+      (None, Some(_)) => Ordering::Greater,
       (None, None) => default_order(),
     }
   }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.timestamp.is_none()
-  }
 }

+ 7 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs

@@ -15,7 +15,7 @@ use crate::entities::{DateCellDataPB, FieldType};
 use crate::services::cell::{
   CellProtobufBlobParser, DecodedCellData, FromCellChangeset, FromCellString, ToCellChangeset,
 };
-use crate::services::field::CELL_DATA;
+use crate::services::field::{TypeOptionCellData, CELL_DATA};
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
 pub struct DateCellChangeset {
@@ -62,6 +62,12 @@ impl DateCellData {
   }
 }
 
+impl TypeOptionCellData for DateCellData {
+  fn is_cell_empty(&self) -> bool {
+    self.timestamp.is_none()
+  }
+}
+
 impl From<&Cell> for DateCellData {
   fn from(cell: &Cell) -> Self {
     let timestamp = cell

+ 35 - 14
frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs

@@ -18,8 +18,9 @@ use crate::services::field::type_options::number_type_option::format::*;
 use crate::services::field::type_options::util::ProtobufStr;
 use crate::services::field::{
   NumberCellFormat, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter, TypeOptionTransform, CELL_DATA,
+  TypeOptionCellDataFilter, TypeOptionCellDataSerde, TypeOptionTransform, CELL_DATA,
 };
+use crate::services::sort::SortCondition;
 
 // Number
 #[derive(Clone, Debug, Serialize, Deserialize)]
@@ -33,6 +34,12 @@ pub struct NumberTypeOption {
 #[derive(Clone, Debug, Default)]
 pub struct NumberCellData(pub String);
 
+impl TypeOptionCellData for NumberCellData {
+  fn is_cell_empty(&self) -> bool {
+    self.0.is_empty()
+  }
+}
+
 impl From<&Cell> for NumberCellData {
   fn from(cell: &Cell) -> Self {
     Self(cell.get_str_value(CELL_DATA).unwrap_or_default())
@@ -95,7 +102,7 @@ impl From<NumberTypeOption> for TypeOptionData {
   }
 }
 
-impl TypeOptionCellData for NumberTypeOption {
+impl TypeOptionCellDataSerde for NumberTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -244,27 +251,41 @@ impl TypeOptionCellDataFilter for NumberTypeOption {
 }
 
 impl TypeOptionCellDataCompare for NumberTypeOption {
+  /// Compares two cell data using a specified sort condition.
+  ///
+  /// The function checks if either `cell_data` or `other_cell_data` is empty (using the `is_empty` method) and:
+  /// - If both are empty, it returns `Ordering::Equal`.
+  /// - If only the left cell is empty, it returns `Ordering::Greater`.
+  /// - If only the right cell is empty, it returns `Ordering::Less`.
+  /// - If neither is empty, the cell data is converted into `NumberCellFormat` and compared based on the decimal value.
+  ///
   fn apply_cmp(
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
-    let left = NumberCellFormat::from_format_str(&cell_data.0, &self.format);
-    let right = NumberCellFormat::from_format_str(&other_cell_data.0, &self.format);
-    match (left, right) {
-      (Ok(left), Ok(right)) => {
-        return left.decimal().cmp(right.decimal());
+    match (cell_data.is_cell_empty(), other_cell_data.is_cell_empty()) {
+      (true, true) => Ordering::Equal,
+      (true, false) => Ordering::Greater,
+      (false, true) => Ordering::Less,
+      (false, false) => {
+        let left = NumberCellFormat::from_format_str(&cell_data.0, &self.format);
+        let right = NumberCellFormat::from_format_str(&other_cell_data.0, &self.format);
+        match (left, right) {
+          (Ok(left), Ok(right)) => {
+            let order = left.decimal().cmp(right.decimal());
+            sort_condition.evaluate_order(order)
+          },
+          (Ok(_), Err(_)) => Ordering::Less,
+          (Err(_), Ok(_)) => Ordering::Greater,
+          (Err(_), Err(_)) => Ordering::Equal,
+        }
       },
-      (Ok(_), Err(_)) => Ordering::Greater,
-      (Err(_), Ok(_)) => Ordering::Less,
-      (Err(_), Err(_)) => Ordering::Equal,
     }
   }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.0.is_empty()
-  }
 }
+
 impl std::default::Default for NumberTypeOption {
   fn default() -> Self {
     let format = NumberFormat::default();

+ 39 - 27
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs

@@ -1,4 +1,4 @@
-use std::cmp::{min, Ordering};
+use std::cmp::Ordering;
 
 use collab::core::any_map::AnyMapExtension;
 use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
@@ -11,9 +11,10 @@ use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
 use crate::services::cell::CellDataChangeset;
 use crate::services::field::{
   default_order, SelectOption, SelectOptionCellChangeset, SelectOptionIds,
-  SelectTypeOptionSharedAction, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter,
+  SelectTypeOptionSharedAction, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOptionCellDataSerde,
 };
+use crate::services::sort::SortCondition;
 
 // Multiple select
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
@@ -47,7 +48,7 @@ impl From<MultiSelectTypeOption> for TypeOptionData {
   }
 }
 
-impl TypeOptionCellData for MultiSelectTypeOption {
+impl TypeOptionCellDataSerde for MultiSelectTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -136,35 +137,46 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption {
 }
 
 impl TypeOptionCellDataCompare for MultiSelectTypeOption {
+  /// Orders two cell values to ensure non-empty cells are moved to the front and empty ones to the back.
+  ///
+  /// This function compares the two provided cell values (`left` and `right`) to determine their
+  /// relative ordering:
+  ///
+  /// - If both cells are empty (`None`), they are considered equal.
+  /// - If the left cell is empty and the right is not, the left cell is ordered to come after the right.
+  /// - If the right cell is empty and the left is not, the left cell is ordered to come before the right.
+  /// - If both cells are non-empty, they are ordered based on their names. If there is an additional sort condition,
+  ///   this condition will further evaluate their order.
+  ///
   fn apply_cmp(
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
-    for i in 0..min(cell_data.len(), other_cell_data.len()) {
-      let order = match (
-        cell_data
-          .get(i)
-          .and_then(|id| self.options.iter().find(|option| &option.id == id)),
-        other_cell_data
-          .get(i)
-          .and_then(|id| self.options.iter().find(|option| &option.id == id)),
-      ) {
-        (Some(left), Some(right)) => left.name.cmp(&right.name),
-        (Some(_), None) => Ordering::Greater,
-        (None, Some(_)) => Ordering::Less,
-        (None, None) => default_order(),
-      };
-
-      if order.is_ne() {
-        return order;
-      }
+    match cell_data.len().cmp(&other_cell_data.len()) {
+      Ordering::Equal => {
+        for (left_id, right_id) in cell_data.iter().zip(other_cell_data.iter()) {
+          let left = self.options.iter().find(|option| &option.id == left_id);
+          let right = self.options.iter().find(|option| &option.id == right_id);
+          let order = match (left, right) {
+            (None, None) => Ordering::Equal,
+            (None, Some(_)) => Ordering::Greater,
+            (Some(_), None) => Ordering::Less,
+            (Some(left_option), Some(right_option)) => {
+              let name_order = left_option.name.cmp(&right_option.name);
+              sort_condition.evaluate_order(name_order)
+            },
+          };
+
+          if order.is_ne() {
+            return order;
+          }
+        }
+        default_order()
+      },
+      order => sort_condition.evaluate_order(order),
     }
-    default_order()
-  }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.is_empty()
   }
 }
 

+ 7 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_ids.rs

@@ -5,7 +5,7 @@ use flowy_error::FlowyResult;
 
 use crate::entities::FieldType;
 use crate::services::cell::{DecodedCellData, FromCellString};
-use crate::services::field::CELL_DATA;
+use crate::services::field::{TypeOptionCellData, CELL_DATA};
 
 pub const SELECTION_IDS_SEPARATOR: &str = ",";
 
@@ -32,6 +32,12 @@ impl SelectOptionIds {
   }
 }
 
+impl TypeOptionCellData for SelectOptionIds {
+  fn is_cell_empty(&self) -> bool {
+    self.is_empty()
+  }
+}
+
 impl FromCellString for SelectOptionIds {
   fn from_cell_str(s: &str) -> FlowyResult<Self>
   where

+ 3 - 2
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs

@@ -13,7 +13,7 @@ use crate::services::field::selection_type_option::type_option_transform::Select
 use crate::services::field::{
   make_selected_options, CheckboxCellData, MultiSelectTypeOption, SelectOption,
   SelectOptionCellData, SelectOptionColor, SelectOptionIds, SingleSelectTypeOption, TypeOption,
-  TypeOptionCellData, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
+  TypeOptionCellDataSerde, TypeOptionTransform, SELECTION_IDS_SEPARATOR,
 };
 
 /// Defines the shared actions used by SingleSelect or Multi-Select.
@@ -122,7 +122,8 @@ where
 
 impl<T> CellDataDecoder for T
 where
-  T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellData,
+  T:
+    SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellDataSerde,
 {
   fn decode_cell(
     &self,

+ 12 - 11
frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs

@@ -1,12 +1,13 @@
 use crate::entities::{FieldType, SelectOptionCellDataPB, SelectOptionFilterPB};
 use crate::services::cell::CellDataChangeset;
 use crate::services::field::{
-  default_order, SelectOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter,
+  default_order, SelectOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOptionCellDataSerde,
 };
 use crate::services::field::{
   SelectOptionCellChangeset, SelectOptionIds, SelectTypeOptionSharedAction,
 };
+use crate::services::sort::SortCondition;
 use collab::core::any_map::AnyMapExtension;
 use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
 use collab_database::rows::Cell;
@@ -46,7 +47,7 @@ impl From<SingleSelectTypeOption> for TypeOptionData {
   }
 }
 
-impl TypeOptionCellData for SingleSelectTypeOption {
+impl TypeOptionCellDataSerde for SingleSelectTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -131,6 +132,7 @@ impl TypeOptionCellDataCompare for SingleSelectTypeOption {
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
     match (
       cell_data
@@ -140,16 +142,15 @@ impl TypeOptionCellDataCompare for SingleSelectTypeOption {
         .first()
         .and_then(|id| self.options.iter().find(|option| &option.id == id)),
     ) {
-      (Some(left), Some(right)) => left.name.cmp(&right.name),
-      (Some(_), None) => Ordering::Greater,
-      (None, Some(_)) => Ordering::Less,
+      (Some(left), Some(right)) => {
+        let order = left.name.cmp(&right.name);
+        sort_condition.evaluate_order(order)
+      },
+      (Some(_), None) => Ordering::Less,
+      (None, Some(_)) => Ordering::Greater,
       (None, None) => default_order(),
     }
   }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.is_empty()
-  }
 }
 
 #[cfg(test)]
@@ -222,6 +223,6 @@ mod tests {
     // delete
     let changeset = SelectOptionCellChangeset::from_delete_options(option_ids);
     let select_option_ids = single_select.apply_changeset(changeset, None).unwrap().1;
-    assert!(select_option_ids.is_empty());
+    assert!(select_option_ids.is_cell_empty());
   }
 }

+ 19 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs

@@ -16,8 +16,9 @@ use crate::services::cell::{
 use crate::services::field::type_options::util::ProtobufStr;
 use crate::services::field::{
   TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
-  TypeOptionTransform, CELL_DATA,
+  TypeOptionCellDataSerde, TypeOptionTransform, CELL_DATA,
 };
+use crate::services::sort::SortCondition;
 
 /// For the moment, the `RichTextTypeOptionPB` is empty. The `data` property is not
 /// used yet.
@@ -85,7 +86,7 @@ impl TypeOptionTransform for RichTextTypeOption {
   }
 }
 
-impl TypeOptionCellData for RichTextTypeOption {
+impl TypeOptionCellDataSerde for RichTextTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -152,12 +153,17 @@ impl TypeOptionCellDataCompare for RichTextTypeOption {
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
-    cell_data.0.cmp(&other_cell_data.0)
-  }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.0.trim().is_empty()
+    match (cell_data.is_cell_empty(), other_cell_data.is_cell_empty()) {
+      (true, true) => Ordering::Equal,
+      (true, false) => Ordering::Greater,
+      (false, true) => Ordering::Less,
+      (false, false) => {
+        let order = cell_data.0.cmp(&other_cell_data.0);
+        sort_condition.evaluate_order(order)
+      },
+    }
   }
 }
 
@@ -221,6 +227,12 @@ impl std::ops::Deref for StrCellData {
   }
 }
 
+impl TypeOptionCellData for StrCellData {
+  fn is_cell_empty(&self) -> bool {
+    self.0.is_empty()
+  }
+}
+
 impl From<&Cell> for StrCellData {
   fn from(cell: &Cell) -> Self {
     Self(cell.get_str_value("data").unwrap_or_default())

+ 35 - 4
frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs

@@ -20,6 +20,7 @@ use crate::services::field::{
   RichTextTypeOption, SingleSelectTypeOption, TimeFormat, URLTypeOption,
 };
 use crate::services::filter::FromFilterString;
+use crate::services::sort::SortCondition;
 
 pub trait TypeOption {
   /// `CellData` represents as the decoded model for current type option. Each of them impl the
@@ -32,7 +33,7 @@ pub trait TypeOption {
   ///
   /// Uses `StrCellData` for any `TypeOption` if their cell data is pure `String`.
   ///
-  type CellData: ToString + Default + Send + Sync + Clone + Debug + 'static;
+  type CellData: TypeOptionCellData + ToString + Default + Send + Sync + Clone + Debug + 'static;
 
   /// Represents as the corresponding field type cell changeset.
   /// The changeset must implements the `FromCellChangesetString` and the `ToCellChangesetString` trait.
@@ -52,8 +53,11 @@ pub trait TypeOption {
   /// Represents as the filter configuration for this type option.
   type CellFilter: FromFilterString + Send + Sync + 'static;
 }
-
-pub trait TypeOptionCellData: TypeOption {
+/// This trait providing serialization and deserialization methods for cell data.
+///
+/// This trait ensures that a type which implements both `TypeOption` and `TypeOptionCellDataSerde` can
+/// be converted to and from a corresponding `Protobuf struct`, and can be parsed from an opaque [Cell] structure.
+pub trait TypeOptionCellDataSerde: TypeOption {
   /// Encode the cell data into corresponding `Protobuf struct`.
   /// For example:
   ///    FieldType::URL => URLCellDataPB
@@ -69,6 +73,18 @@ pub trait TypeOptionCellData: TypeOption {
   fn parse_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
 }
 
+/// This trait that provides methods to extend the [TypeOption::CellData] functionalities.
+///
+pub trait TypeOptionCellData {
+  /// Checks if the cell content is considered empty.
+  ///
+  /// Even if a cell is initialized, its content might still be considered empty
+  /// based on certain criteria. e.g. empty text, date, select option, etc.
+  fn is_cell_empty(&self) -> bool {
+    false
+  }
+}
+
 pub trait TypeOptionTransform: TypeOption {
   /// Returns true if the current `TypeOption` provides custom type option transformation
   fn transformable(&self) -> bool {
@@ -127,13 +143,28 @@ pub fn default_order() -> Ordering {
 }
 
 pub trait TypeOptionCellDataCompare: TypeOption {
+  /// Compares the cell contents of two cells that are both not
+  /// None. However, the cell contents might still be empty
   fn apply_cmp(
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering;
 
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool;
+  /// Compares the two cells where one of the cells is None
+  fn apply_cmp_with_uninitialized(
+    &self,
+    cell_data: Option<&<Self as TypeOption>::CellData>,
+    other_cell_data: Option<&<Self as TypeOption>::CellData>,
+    _sort_condition: SortCondition,
+  ) -> Ordering {
+    match (cell_data, other_cell_data) {
+      (None, Some(cell_data)) if !cell_data.is_cell_empty() => Ordering::Greater,
+      (Some(cell_data), None) if !cell_data.is_cell_empty() => Ordering::Less,
+      _ => Ordering::Equal,
+    }
+  }
 }
 
 pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(

+ 55 - 26
frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs

@@ -16,8 +16,8 @@ use crate::services::cell::{
 use crate::services::field::checklist_type_option::ChecklistTypeOption;
 use crate::services::field::{
   CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RichTextTypeOption,
-  SingleSelectTypeOption, TypeOption, TypeOptionCellData, TypeOptionCellDataCompare,
-  TypeOptionCellDataFilter, TypeOptionTransform, URLTypeOption,
+  SingleSelectTypeOption, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOptionCellDataSerde, TypeOptionTransform, URLTypeOption,
 };
 use crate::services::sort::SortCondition;
 
@@ -48,8 +48,8 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
 
   fn handle_cell_compare(
     &self,
-    left_cell: &Cell,
-    right_cell: &Cell,
+    left_cell: Option<&Cell>,
+    right_cell: Option<&Cell>,
     field: &Field,
     sort_condition: SortCondition,
   ) -> Ordering;
@@ -105,7 +105,7 @@ where
   T: TypeOption
     + CellDataDecoder
     + CellDataChangeset
-    + TypeOptionCellData
+    + TypeOptionCellDataSerde
     + TypeOptionTransform
     + TypeOptionCellDataFilter
     + TypeOptionCellDataCompare
@@ -208,7 +208,7 @@ where
   T: TypeOption
     + CellDataDecoder
     + CellDataChangeset
-    + TypeOptionCellData
+    + TypeOptionCellDataSerde
     + TypeOptionTransform
     + TypeOptionCellDataFilter
     + TypeOptionCellDataCompare
@@ -241,32 +241,61 @@ where
     Ok(cell)
   }
 
+  /// Compares two cell data values given their optional references, field information, and sorting condition.
+  ///
+  /// This function is designed to handle the comparison of cells that might not be initialized. The cells are
+  /// first decoded based on the provided field type, and then compared according to the specified sort condition.
+  ///
+  /// # Parameters
+  /// - `left_cell`: An optional reference to the left cell's data.
+  /// - `right_cell`: An optional reference to the right cell's data.
+  /// - `field`: A reference to the field information, which includes details about the field type.
+  /// - `sort_condition`: The condition that dictates the sort order based on the results of the comparison.
+  ///
+  /// # Returns
+  /// An `Ordering` indicating:
+  /// - `Ordering::Equal` if both cells are `None` or if their decoded values are equal.
+  /// - `Ordering::Less` or `Ordering::Greater` based on the `apply_cmp_with_uninitialized` or `apply_cmp`
+  ///   method results and the specified `sort_condition`.
+  ///
+  /// # Note
+  /// - If only one of the cells is `None`, the other cell is decoded, and the comparison is made using
+  ///   the `apply_cmp_with_uninitialized` method.
+  /// - If both cells are present, they are decoded, and the comparison is made using the `apply_cmp` method.
   fn handle_cell_compare(
     &self,
-    left_cell: &Cell,
-    right_cell: &Cell,
+    left_cell: Option<&Cell>,
+    right_cell: Option<&Cell>,
     field: &Field,
     sort_condition: SortCondition,
   ) -> Ordering {
     let field_type = FieldType::from(field.field_type);
-    let left = self
-      .get_decoded_cell_data(left_cell, &field_type, field)
-      .unwrap_or_default();
-    let right = self
-      .get_decoded_cell_data(right_cell, &field_type, field)
-      .unwrap_or_default();
-
-    match (self.exempt_from_cmp(&left), self.exempt_from_cmp(&right)) {
-      (true, true) => Ordering::Equal,
-      (true, false) => Ordering::Greater,
-      (false, true) => Ordering::Less,
-      (false, false) => {
-        let order = self.apply_cmp(&left, &right);
-        // The order is calculated by Ascending. So reverse the order if the SortCondition is descending.
-        match sort_condition {
-          SortCondition::Ascending => order,
-          SortCondition::Descending => order.reverse(),
-        }
+
+    match (left_cell, right_cell) {
+      (None, None) => Ordering::Equal,
+      (None, Some(right_cell)) => {
+        let right_cell_data = self
+          .get_decoded_cell_data(right_cell, &field_type, field)
+          .unwrap_or_default();
+
+        self.apply_cmp_with_uninitialized(None, Some(right_cell_data).as_ref(), sort_condition)
+      },
+      (Some(left_cell), None) => {
+        let left_cell_data = self
+          .get_decoded_cell_data(left_cell, &field_type, field)
+          .unwrap_or_default();
+
+        self.apply_cmp_with_uninitialized(Some(left_cell_data).as_ref(), None, sort_condition)
+      },
+      (Some(left_cell), Some(right_cell)) => {
+        let left_cell_data: <T as TypeOption>::CellData = self
+          .get_decoded_cell_data(left_cell, &field_type, field)
+          .unwrap_or_default();
+        let right_cell_data = self
+          .get_decoded_cell_data(right_cell, &field_type, field)
+          .unwrap_or_default();
+
+        self.apply_cmp(&left_cell_data, &right_cell_data, sort_condition)
       },
     }
   }

+ 15 - 7
frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs

@@ -1,9 +1,10 @@
 use crate::entities::{FieldType, TextFilterPB, URLCellDataPB};
 use crate::services::cell::{CellDataChangeset, CellDataDecoder};
 use crate::services::field::{
-  TypeOption, TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
+  TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
   TypeOptionTransform, URLCellData,
 };
+use crate::services::sort::SortCondition;
 
 use collab::core::any_map::AnyMapExtension;
 use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
@@ -46,7 +47,7 @@ impl From<URLTypeOption> for TypeOptionData {
 
 impl TypeOptionTransform for URLTypeOption {}
 
-impl TypeOptionCellData for URLTypeOption {
+impl TypeOptionCellDataSerde for URLTypeOption {
   fn protobuf_encode(
     &self,
     cell_data: <Self as TypeOption>::CellData,
@@ -123,12 +124,19 @@ impl TypeOptionCellDataCompare for URLTypeOption {
     &self,
     cell_data: &<Self as TypeOption>::CellData,
     other_cell_data: &<Self as TypeOption>::CellData,
+    sort_condition: SortCondition,
   ) -> Ordering {
-    cell_data.data.cmp(&other_cell_data.data)
-  }
-
-  fn exempt_from_cmp(&self, cell_data: &<Self as TypeOption>::CellData) -> bool {
-    cell_data.data.is_empty()
+    let is_left_empty = cell_data.data.is_empty();
+    let is_right_empty = other_cell_data.data.is_empty();
+    match (is_left_empty, is_right_empty) {
+      (true, true) => Ordering::Equal,
+      (true, false) => Ordering::Greater,
+      (false, true) => Ordering::Less,
+      (false, false) => {
+        let order = cell_data.data.cmp(&other_cell_data.data);
+        sort_condition.evaluate_order(order)
+      },
+    }
   }
 }
 

+ 7 - 1
frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option_entities.rs

@@ -7,7 +7,7 @@ use flowy_error::{internal_error, FlowyResult};
 
 use crate::entities::{FieldType, URLCellDataPB};
 use crate::services::cell::{CellProtobufBlobParser, DecodedCellData, FromCellString};
-use crate::services::field::CELL_DATA;
+use crate::services::field::{TypeOptionCellData, CELL_DATA};
 
 #[derive(Clone, Debug, Default, Serialize, Deserialize)]
 pub struct URLCellData {
@@ -28,6 +28,12 @@ impl URLCellData {
   }
 }
 
+impl TypeOptionCellData for URLCellData {
+  fn is_cell_empty(&self) -> bool {
+    self.data.is_empty()
+  }
+}
+
 impl From<&Cell> for URLCellData {
   fn from(cell: &Cell) -> Self {
     let url = cell.get_str_value("url").unwrap_or_default();

+ 9 - 34
frontend/rust-lib/flowy-database2/src/services/sort/controller.rs

@@ -246,46 +246,20 @@ fn cmp_row(
     .find(|field_rev| field_rev.id == sort.field_id)
   {
     None => default_order(),
-    Some(field_rev) => match (
+    Some(field_rev) => cmp_cell(
       left.cells.get(&sort.field_id),
       right.cells.get(&sort.field_id),
-    ) {
-      (Some(left_cell), Some(right_cell)) => cmp_cell(
-        left_cell,
-        right_cell,
-        field_rev,
-        field_type,
-        cell_data_cache,
-        sort.condition,
-      ),
-      (Some(_), None) => {
-        if field_type.is_checkbox() {
-          match sort.condition {
-            SortCondition::Ascending => Ordering::Greater,
-            SortCondition::Descending => Ordering::Less,
-          }
-        } else {
-          Ordering::Less
-        }
-      },
-      (None, Some(_)) => {
-        if field_type.is_checkbox() {
-          match sort.condition {
-            SortCondition::Ascending => Ordering::Less,
-            SortCondition::Descending => Ordering::Greater,
-          }
-        } else {
-          Ordering::Greater
-        }
-      },
-      _ => default_order(),
-    },
+      field_rev,
+      field_type,
+      cell_data_cache,
+      sort.condition,
+    ),
   }
 }
 
 fn cmp_cell(
-  left_cell: &Cell,
-  right_cell: &Cell,
+  left_cell: Option<&Cell>,
+  right_cell: Option<&Cell>,
   field: &Arc<Field>,
   field_type: FieldType,
   cell_data_cache: &CellCache,
@@ -306,6 +280,7 @@ fn cmp_cell(
     },
   }
 }
+
 #[derive(Serialize, Deserialize, Clone, Debug)]
 enum SortEvent {
   SortDidChanged,

+ 12 - 0
frontend/rust-lib/flowy-database2/src/services/sort/entities.rs

@@ -1,3 +1,5 @@
+use std::cmp::Ordering;
+
 use anyhow::bail;
 use collab::core::any_map::AnyMapExtension;
 use collab_database::rows::RowId;
@@ -67,6 +69,16 @@ impl SortCondition {
   pub fn value(&self) -> i64 {
     *self as i64
   }
+
+  /// Given an [Ordering] resulting from a comparison,
+  /// reverse it if the sort condition is descending rather than
+  /// the default ascending
+  pub fn evaluate_order(&self, order: Ordering) -> Ordering {
+    match self {
+      SortCondition::Ascending => order,
+      SortCondition::Descending => order.reverse(),
+    }
+  }
 }
 
 impl Default for SortCondition {

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs

@@ -22,7 +22,7 @@ async fn grid_filter_checkbox_is_check_test() {
 #[tokio::test]
 async fn grid_filter_checkbox_is_uncheck_test() {
   let mut test = DatabaseFilterTest::new().await;
-  let expected = 3;
+  let expected = 4;
   let row_count = test.row_details.len();
   let scripts = vec![
     CreateCheckboxFilter {

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs

@@ -6,7 +6,7 @@ use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}
 #[tokio::test]
 async fn grid_filter_checklist_is_incomplete_test() {
   let mut test = DatabaseFilterTest::new().await;
-  let expected = 5;
+  let expected = 6;
   let row_count = test.row_details.len();
   let scripts = vec![
     UpdateChecklistCell {

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs

@@ -84,7 +84,7 @@ async fn grid_filter_number_is_less_than_or_equal_test() {
 async fn grid_filter_number_is_empty_test() {
   let mut test = DatabaseFilterTest::new().await;
   let row_count = test.row_details.len();
-  let expected = 1;
+  let expected = 2;
   let scripts = vec![
     CreateNumberFilter {
       condition: NumberFilterConditionPB::NumberIsEmpty,

+ 5 - 5
frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs

@@ -11,7 +11,7 @@ async fn grid_filter_multi_select_is_empty_test() {
       condition: SelectOptionConditionPB::OptionIsEmpty,
       option_ids: vec![],
     },
-    AssertNumberOfVisibleRows { expected: 3 },
+    AssertNumberOfVisibleRows { expected: 2 },
   ];
   test.run_scripts(scripts).await;
 }
@@ -24,7 +24,7 @@ async fn grid_filter_multi_select_is_not_empty_test() {
       condition: SelectOptionConditionPB::OptionIsNotEmpty,
       option_ids: vec![],
     },
-    AssertNumberOfVisibleRows { expected: 3 },
+    AssertNumberOfVisibleRows { expected: 5 },
   ];
   test.run_scripts(scripts).await;
 }
@@ -39,7 +39,7 @@ async fn grid_filter_multi_select_is_test() {
       condition: SelectOptionConditionPB::OptionIs,
       option_ids: vec![options.remove(0).id, options.remove(0).id],
     },
-    AssertNumberOfVisibleRows { expected: 3 },
+    AssertNumberOfVisibleRows { expected: 5 },
   ];
   test.run_scripts(scripts).await;
 }
@@ -54,7 +54,7 @@ async fn grid_filter_multi_select_is_test2() {
       condition: SelectOptionConditionPB::OptionIs,
       option_ids: vec![options.remove(1).id],
     },
-    AssertNumberOfVisibleRows { expected: 2 },
+    AssertNumberOfVisibleRows { expected: 4 },
   ];
   test.run_scripts(scripts).await;
 }
@@ -62,7 +62,7 @@ async fn grid_filter_multi_select_is_test2() {
 #[tokio::test]
 async fn grid_filter_single_select_is_empty_test() {
   let mut test = DatabaseFilterTest::new().await;
-  let expected = 2;
+  let expected = 3;
   let row_count = test.row_details.len();
   let scripts = vec![
     CreateSingleSelectFilter {

+ 1 - 1
frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs

@@ -213,7 +213,7 @@ async fn grid_filter_delete_test() {
         changed: None,
       },
       AssertFilterCount { count: 0 },
-      AssertNumberOfVisibleRows { expected: 6 },
+      AssertNumberOfVisibleRows { expected: 7 },
     ])
     .await;
 }

+ 12 - 5
frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs

@@ -116,7 +116,7 @@ pub fn make_test_grid() -> DatabaseData {
     }
   }
 
-  for i in 0..6 {
+  for i in 0..7 {
     let mut row_builder = TestRowBuilder::new(gen_row_id(), &fields);
     match i {
       0 => {
@@ -166,9 +166,9 @@ pub fn make_test_grid() -> DatabaseData {
             FieldType::SingleSelect => {
               row_builder.insert_single_select_cell(|mut options| options.remove(0))
             },
-            FieldType::MultiSelect => {
-              row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
-            },
+            FieldType::MultiSelect => row_builder.insert_multi_select_cell(|mut options| {
+              vec![options.remove(1), options.remove(0), options.remove(0)]
+            }),
             FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
             _ => "".to_owned(),
           };
@@ -201,7 +201,8 @@ pub fn make_test_grid() -> DatabaseData {
             FieldType::SingleSelect => {
               row_builder.insert_single_select_cell(|mut options| options.remove(1))
             },
-
+            FieldType::MultiSelect => row_builder
+              .insert_multi_select_cell(|mut options| vec![options.remove(1), options.remove(1)]),
             FieldType::Checkbox => row_builder.insert_checkbox_cell("false"),
             _ => "".to_owned(),
           };
@@ -218,11 +219,17 @@ pub fn make_test_grid() -> DatabaseData {
             FieldType::SingleSelect => {
               row_builder.insert_single_select_cell(|mut options| options.remove(1))
             },
+            FieldType::MultiSelect => {
+              row_builder.insert_multi_select_cell(|mut options| vec![options.remove(1)])
+            },
             FieldType::Checkbox => row_builder.insert_checkbox_cell("true"),
             _ => "".to_owned(),
           };
         }
       },
+      6 => {
+        row_builder.insert_text_cell("CB");
+      },
       _ => {},
     }
 

+ 4 - 3
frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs

@@ -30,10 +30,11 @@ async fn export_csv_test() {
   let expected = r#"Name,Price,Time,Status,Platform,is urgent,link,TODO,Updated At,Created At
 A,$1,2022/03/14,,"Google,Facebook",Yes,AppFlowy website - https://www.appflowy.io,,2022/03/14,2022/03/14
 ,$2,2022/03/14,,"Google,Twitter",Yes,,,2022/03/14,2022/03/14
-C,$3,2022/03/14,Completed,Facebook,No,,,2022/03/14,2022/03/14
+C,$3,2022/03/14,Completed,"Facebook,Google,Twitter",No,,,2022/03/14,2022/03/14
 DA,$14,2022/11/17,Completed,,No,,,2022/11/17,2022/11/17
-AE,,2022/11/13,Planned,,No,,,2022/11/13,2022/11/13
-AE,$5,2022/12/25,Planned,,Yes,,,2022/12/25,2022/12/25
+AE,,2022/11/13,Planned,"Facebook,Twitter",No,,,2022/11/13,2022/11/13
+AE,$5,2022/12/25,Planned,Facebook,Yes,,,2022/12/25,2022/12/25
+CB,,,,,,,,,
 "#;
   println!("{}", s);
   assert_eq!(s, expected);

+ 0 - 49
frontend/rust-lib/flowy-database2/tests/database/sort_test/checkbox_and_text_test.rs

@@ -1,49 +0,0 @@
-use flowy_database2::entities::FieldType;
-use flowy_database2::services::sort::SortCondition;
-
-use crate::database::sort_test::script::DatabaseSortTest;
-use crate::database::sort_test::script::SortScript::{AssertCellContentOrder, InsertSort};
-
-#[tokio::test]
-async fn sort_checkbox_and_then_text_by_descending_test() {
-  let mut test = DatabaseSortTest::new().await;
-  let checkbox_field = test.get_first_field(FieldType::Checkbox);
-  let text_field = test.get_first_field(FieldType::RichText);
-  let scripts = vec![
-    AssertCellContentOrder {
-      field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
-    },
-    AssertCellContentOrder {
-      field_id: text_field.id.clone(),
-      orders: vec!["A", "", "C", "DA", "AE", "AE"],
-    },
-    // Insert checkbox sort
-    InsertSort {
-      field: checkbox_field.clone(),
-      condition: SortCondition::Descending,
-    },
-    AssertCellContentOrder {
-      field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
-    },
-    AssertCellContentOrder {
-      field_id: text_field.id.clone(),
-      orders: vec!["A", "", "AE", "C", "DA", "AE"],
-    },
-    // Insert text sort
-    InsertSort {
-      field: text_field.clone(),
-      condition: SortCondition::Ascending,
-    },
-    AssertCellContentOrder {
-      field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
-    },
-    AssertCellContentOrder {
-      field_id: text_field.id.clone(),
-      orders: vec!["A", "AE", "", "AE", "C", "DA"],
-    },
-  ];
-  test.run_scripts(scripts).await;
-}

+ 0 - 1
frontend/rust-lib/flowy-database2/tests/database/sort_test/mod.rs

@@ -1,4 +1,3 @@
-mod checkbox_and_text_test;
 mod multi_sort_test;
 mod script;
 mod single_sort_test;

+ 21 - 23
frontend/rust-lib/flowy-database2/tests/database/sort_test/multi_sort_test.rs

@@ -5,46 +5,44 @@ use crate::database::sort_test::script::DatabaseSortTest;
 use crate::database::sort_test::script::SortScript::*;
 
 #[tokio::test]
-async fn sort_text_with_checkbox_by_ascending_test() {
+async fn sort_checkbox_and_then_text_by_descending_test() {
   let mut test = DatabaseSortTest::new().await;
-  let text_field = test.get_first_field(FieldType::RichText).clone();
-  let checkbox_field = test.get_first_field(FieldType::Checkbox).clone();
+  let checkbox_field = test.get_first_field(FieldType::Checkbox);
+  let text_field = test.get_first_field(FieldType::RichText);
   let scripts = vec![
-    AssertCellContentOrder {
-      field_id: text_field.id.clone(),
-      orders: vec!["A", "", "C", "DA", "AE", "AE"],
-    },
     AssertCellContentOrder {
       field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
-    },
-    InsertSort {
-      field: text_field.clone(),
-      condition: SortCondition::Ascending,
+      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes", ""],
     },
     AssertCellContentOrder {
       field_id: text_field.id.clone(),
-      orders: vec!["A", "AE", "AE", "C", "DA", ""],
-    },
-    AssertCellContentOrder {
-      field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "No", "Yes", "No", "No", "Yes"],
+      orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
     },
-  ];
-  test.run_scripts(scripts).await;
-
-  let scripts = vec![
+    // Insert checkbox sort
     InsertSort {
       field: checkbox_field.clone(),
       condition: SortCondition::Descending,
     },
+    AssertCellContentOrder {
+      field_id: checkbox_field.id.clone(),
+      orders: vec!["Yes", "Yes", "Yes", "No", "No", "No", ""],
+    },
     AssertCellContentOrder {
       field_id: text_field.id.clone(),
-      orders: vec!["A", "AE", "AE", "C", "DA", ""],
+      orders: vec!["A", "", "AE", "C", "DA", "AE", "CB"],
+    },
+    // Insert text sort
+    InsertSort {
+      field: text_field.clone(),
+      condition: SortCondition::Ascending,
     },
     AssertCellContentOrder {
       field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
+      orders: vec!["Yes", "Yes", "Yes", "No", "No", "", "No"],
+    },
+    AssertCellContentOrder {
+      field_id: text_field.id.clone(),
+      orders: vec!["A", "AE", "", "AE", "C", "CB", "DA"],
     },
   ];
   test.run_scripts(scripts).await;

+ 145 - 36
frontend/rust-lib/flowy-database2/tests/database/sort_test/single_sort_test.rs

@@ -10,7 +10,7 @@ async fn sort_text_by_ascending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: text_field.id.clone(),
-      orders: vec!["A", "", "C", "DA", "AE", "AE"],
+      orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
     },
     InsertSort {
       field: text_field.clone(),
@@ -18,7 +18,28 @@ async fn sort_text_by_ascending_test() {
     },
     AssertCellContentOrder {
       field_id: text_field.id.clone(),
-      orders: vec!["A", "AE", "AE", "C", "DA", ""],
+      orders: vec!["A", "AE", "AE", "C", "CB", "DA", ""],
+    },
+  ];
+  test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_text_by_descending_test() {
+  let mut test = DatabaseSortTest::new().await;
+  let text_field = test.get_first_field(FieldType::RichText);
+  let scripts = vec![
+    AssertCellContentOrder {
+      field_id: text_field.id.clone(),
+      orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
+    },
+    InsertSort {
+      field: text_field.clone(),
+      condition: SortCondition::Descending,
+    },
+    AssertCellContentOrder {
+      field_id: text_field.id.clone(),
+      orders: vec!["DA", "CB", "C", "AE", "AE", "A", ""],
     },
   ];
   test.run_scripts(scripts).await;
@@ -29,13 +50,17 @@ async fn sort_change_notification_by_update_text_test() {
   let mut test = DatabaseSortTest::new().await;
   let text_field = test.get_first_field(FieldType::RichText).clone();
   let scripts = vec![
+    AssertCellContentOrder {
+      field_id: text_field.id.clone(),
+      orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
+    },
     InsertSort {
       field: text_field.clone(),
       condition: SortCondition::Ascending,
     },
     AssertCellContentOrder {
       field_id: text_field.id.clone(),
-      orders: vec!["A", "AE", "AE", "C", "DA", ""],
+      orders: vec!["A", "AE", "AE", "C", "CB", "DA", ""],
     },
     // Wait the insert task to finish. The cost of time should be less than 200 milliseconds.
     Wait { millis: 200 },
@@ -49,8 +74,8 @@ async fn sort_change_notification_by_update_text_test() {
       text: "E".to_string(),
     },
     AssertSortChanged {
-      old_row_orders: vec!["A", "E", "AE", "C", "DA", ""],
-      new_row_orders: vec!["A", "AE", "C", "DA", "E", ""],
+      old_row_orders: vec!["A", "E", "AE", "C", "CB", "DA", ""],
+      new_row_orders: vec!["A", "AE", "C", "CB", "DA", "E", ""],
     },
   ];
   test.run_scripts(scripts).await;
@@ -73,28 +98,7 @@ async fn sort_text_by_ascending_and_delete_sort_test() {
     },
     AssertCellContentOrder {
       field_id: text_field.id.clone(),
-      orders: vec!["A", "", "C", "DA", "AE"],
-    },
-  ];
-  test.run_scripts(scripts).await;
-}
-
-#[tokio::test]
-async fn sort_text_by_descending_test() {
-  let mut test = DatabaseSortTest::new().await;
-  let text_field = test.get_first_field(FieldType::RichText);
-  let scripts = vec![
-    AssertCellContentOrder {
-      field_id: text_field.id.clone(),
-      orders: vec!["A", "", "C", "DA", "AE", "AE"],
-    },
-    InsertSort {
-      field: text_field.clone(),
-      condition: SortCondition::Descending,
-    },
-    AssertCellContentOrder {
-      field_id: text_field.id.clone(),
-      orders: vec!["DA", "C", "AE", "AE", "A", ""],
+      orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"],
     },
   ];
   test.run_scripts(scripts).await;
@@ -107,12 +111,16 @@ async fn sort_checkbox_by_ascending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "No", "No", "No"],
+      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes", ""],
     },
     InsertSort {
       field: checkbox_field.clone(),
       condition: SortCondition::Ascending,
     },
+    AssertCellContentOrder {
+      field_id: checkbox_field.id.clone(),
+      orders: vec!["No", "No", "No", "", "Yes", "Yes", "Yes"],
+    },
   ];
   test.run_scripts(scripts).await;
 }
@@ -124,7 +132,7 @@ async fn sort_checkbox_by_descending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes"],
+      orders: vec!["Yes", "Yes", "No", "No", "No", "Yes", ""],
     },
     InsertSort {
       field: checkbox_field.clone(),
@@ -132,7 +140,7 @@ async fn sort_checkbox_by_descending_test() {
     },
     AssertCellContentOrder {
       field_id: checkbox_field.id.clone(),
-      orders: vec!["Yes", "Yes", "Yes", "No", "No", "No"],
+      orders: vec!["Yes", "Yes", "Yes", "No", "No", "No", ""],
     },
   ];
   test.run_scripts(scripts).await;
@@ -151,6 +159,8 @@ async fn sort_date_by_ascending_test() {
         "2022/03/14",
         "2022/11/17",
         "2022/11/13",
+        "2022/12/25",
+        "",
       ],
     },
     InsertSort {
@@ -165,6 +175,8 @@ async fn sort_date_by_ascending_test() {
         "2022/03/14",
         "2022/11/13",
         "2022/11/17",
+        "2022/12/25",
+        "",
       ],
     },
   ];
@@ -185,6 +197,7 @@ async fn sort_date_by_descending_test() {
         "2022/11/17",
         "2022/11/13",
         "2022/12/25",
+        "",
       ],
     },
     InsertSort {
@@ -200,12 +213,34 @@ async fn sort_date_by_descending_test() {
         "2022/03/14",
         "2022/03/14",
         "2022/03/14",
+        "",
       ],
     },
   ];
   test.run_scripts(scripts).await;
 }
 
+#[tokio::test]
+async fn sort_number_by_ascending_test() {
+  let mut test = DatabaseSortTest::new().await;
+  let number_field = test.get_first_field(FieldType::Number);
+  let scripts = vec![
+    AssertCellContentOrder {
+      field_id: number_field.id.clone(),
+      orders: vec!["$1", "$2", "$3", "$14", "", "$5", ""],
+    },
+    InsertSort {
+      field: number_field.clone(),
+      condition: SortCondition::Ascending,
+    },
+    AssertCellContentOrder {
+      field_id: number_field.id.clone(),
+      orders: vec!["$1", "$2", "$3", "$5", "$14", "", ""],
+    },
+  ];
+  test.run_scripts(scripts).await;
+}
+
 #[tokio::test]
 async fn sort_number_by_descending_test() {
   let mut test = DatabaseSortTest::new().await;
@@ -213,7 +248,7 @@ async fn sort_number_by_descending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: number_field.id.clone(),
-      orders: vec!["$1", "$2", "$3", "$14", "", "$5"],
+      orders: vec!["$1", "$2", "$3", "$14", "", "$5", ""],
     },
     InsertSort {
       field: number_field.clone(),
@@ -221,7 +256,28 @@ async fn sort_number_by_descending_test() {
     },
     AssertCellContentOrder {
       field_id: number_field.id.clone(),
-      orders: vec!["$14", "$5", "$3", "$2", "$1", ""],
+      orders: vec!["$14", "$5", "$3", "$2", "$1", "", ""],
+    },
+  ];
+  test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_single_select_by_ascending_test() {
+  let mut test = DatabaseSortTest::new().await;
+  let single_select = test.get_first_field(FieldType::SingleSelect);
+  let scripts = vec![
+    AssertCellContentOrder {
+      field_id: single_select.id.clone(),
+      orders: vec!["", "", "Completed", "Completed", "Planned", "Planned", ""],
+    },
+    InsertSort {
+      field: single_select.clone(),
+      condition: SortCondition::Ascending,
+    },
+    AssertCellContentOrder {
+      field_id: single_select.id.clone(),
+      orders: vec!["Completed", "Completed", "Planned", "Planned", "", "", ""],
     },
   ];
   test.run_scripts(scripts).await;
@@ -234,7 +290,7 @@ async fn sort_single_select_by_descending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: single_select.id.clone(),
-      orders: vec!["", "", "Completed", "Completed", "Planned", "Planned"],
+      orders: vec!["", "", "Completed", "Completed", "Planned", "Planned", ""],
     },
     InsertSort {
       field: single_select.clone(),
@@ -242,7 +298,7 @@ async fn sort_single_select_by_descending_test() {
     },
     AssertCellContentOrder {
       field_id: single_select.id.clone(),
-      orders: vec!["Planned", "Planned", "Completed", "Completed", "", ""],
+      orders: vec!["Planned", "Planned", "Completed", "Completed", "", "", ""],
     },
   ];
   test.run_scripts(scripts).await;
@@ -255,7 +311,15 @@ async fn sort_multi_select_by_ascending_test() {
   let scripts = vec![
     AssertCellContentOrder {
       field_id: multi_select.id.clone(),
-      orders: vec!["Google,Facebook", "Google,Twitter", "Facebook", "", "", ""],
+      orders: vec![
+        "Google,Facebook",
+        "Google,Twitter",
+        "Facebook,Google,Twitter",
+        "",
+        "Facebook,Twitter",
+        "Facebook",
+        "",
+      ],
     },
     InsertSort {
       field: multi_select.clone(),
@@ -263,7 +327,52 @@ async fn sort_multi_select_by_ascending_test() {
     },
     AssertCellContentOrder {
       field_id: multi_select.id.clone(),
-      orders: vec!["Facebook", "Google,Facebook", "Google,Twitter", "", "", ""],
+      orders: vec![
+        "Facebook",
+        "Facebook,Twitter",
+        "Google,Facebook",
+        "Google,Twitter",
+        "Facebook,Google,Twitter",
+        "",
+        "",
+      ],
+    },
+  ];
+  test.run_scripts(scripts).await;
+}
+
+#[tokio::test]
+async fn sort_multi_select_by_descending_test() {
+  let mut test = DatabaseSortTest::new().await;
+  let multi_select = test.get_first_field(FieldType::MultiSelect);
+  let scripts = vec![
+    AssertCellContentOrder {
+      field_id: multi_select.id.clone(),
+      orders: vec![
+        "Google,Facebook",
+        "Google,Twitter",
+        "Facebook,Google,Twitter",
+        "",
+        "Facebook,Twitter",
+        "Facebook",
+        "",
+      ],
+    },
+    InsertSort {
+      field: multi_select.clone(),
+      condition: SortCondition::Descending,
+    },
+    AssertCellContentOrder {
+      field_id: multi_select.id.clone(),
+      orders: vec![
+        "Facebook,Google,Twitter",
+        "Google,Twitter",
+        "Google,Facebook",
+        "Facebook,Twitter",
+        "Facebook",
+        "",
+        "",
+      ],
     },
   ];
   test.run_scripts(scripts).await;

+ 7 - 7
frontend/rust-lib/flowy-folder2/src/entities/icon.rs

@@ -21,9 +21,9 @@ impl std::convert::From<ViewIconTypePB> for IconType {
   }
 }
 
-impl Into<ViewIconTypePB> for IconType {
-  fn into(self) -> ViewIconTypePB {
-    match self {
+impl From<IconType> for ViewIconTypePB {
+  fn from(val: IconType) -> Self {
+    match val {
       IconType::Emoji => ViewIconTypePB::Emoji,
       IconType::Url => ViewIconTypePB::Url,
       IconType::Icon => ViewIconTypePB::Icon,
@@ -48,11 +48,11 @@ impl std::convert::From<ViewIconPB> for ViewIcon {
   }
 }
 
-impl Into<ViewIconPB> for ViewIcon {
-  fn into(self) -> ViewIconPB {
+impl From<ViewIcon> for ViewIconPB {
+  fn from(val: ViewIcon) -> Self {
     ViewIconPB {
-      ty: self.ty.into(),
-      value: self.value,
+      ty: val.ty.into(),
+      value: val.value,
     }
   }
 }

+ 2 - 2
frontend/rust-lib/flowy-test/src/lib.rs

@@ -45,7 +45,7 @@ pub struct FlowyCoreTest {
 
 impl Default for FlowyCoreTest {
   fn default() -> Self {
-    let temp_dir = PathBuf::from(temp_dir()).join(nanoid!(6));
+    let temp_dir = temp_dir().join(nanoid!(6));
     std::fs::create_dir_all(&temp_dir).unwrap();
     Self::new_with_user_data_path(temp_dir, nanoid!(6))
   }
@@ -57,7 +57,7 @@ impl FlowyCoreTest {
   }
 
   pub fn new_with_user_data_path(path: PathBuf, name: String) -> Self {
-    let config = AppFlowyCoreConfig::new(path.clone().to_str().unwrap(), name).log_filter(
+    let config = AppFlowyCoreConfig::new(path.to_str().unwrap(), name).log_filter(
       "info",
       vec!["flowy_test".to_string(), "lib_dispatch".to_string()],
     );

+ 2 - 2
frontend/rust-lib/flowy-test/tests/folder/local_test/subscription_test.rs

@@ -102,7 +102,7 @@ async fn update_view_subscription_test() {
 
   let cloned_test = test.clone();
   let view = workspace.views.pop().unwrap();
-  assert_eq!(view.is_favorite, false);
+  assert!(!view.is_favorite);
 
   let update_view_id = view.id.clone();
   tokio::spawn(async move {
@@ -123,5 +123,5 @@ async fn update_view_subscription_test() {
   let expected_view = update.update_child_views.first().unwrap();
   assert_eq!(expected_view.id, view.id);
   assert_eq!(expected_view.name, "hello world".to_string());
-  assert_eq!(expected_view.is_favorite, true);
+  assert!(expected_view.is_favorite);
 }

+ 1 - 1
frontend/rust-lib/flowy-user/src/event_map.rs

@@ -17,7 +17,7 @@ use crate::{errors::FlowyError, services::UserSession};
 pub fn init(user_session: Weak<UserSession>) -> AFPlugin {
   let store_preferences = user_session
     .upgrade()
-    .and_then(|session| Some(session.get_store_preferences()))
+    .map(|session| session.get_store_preferences())
     .unwrap();
   AFPlugin::new()
     .name("Flowy-User")