diff --git a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart index f6b74624d0..237f38a4fa 100644 --- a/frontend/appflowy_flutter/lib/plugins/database_view/application/field/field_controller.dart +++ b/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; diff --git a/frontend/rust-lib/flowy-core/src/module.rs b/frontend/rust-lib/flowy-core/src/module.rs index f9b17f6333..3ce413ff35 100644 --- a/frontend/rust-lib/flowy-core/src/module.rs +++ b/frontend/rust-lib/flowy-core/src/module.rs @@ -14,7 +14,7 @@ pub fn make_plugins( ) -> Vec { 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); diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs index e63cd60243..090691d2c3 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/layout_deps.rs +++ b/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) diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs index 35a482a653..7645bf2564 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option.rs +++ b/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 for TypeOptionData { } } -impl TypeOptionCellData for CheckboxTypeOption { +impl TypeOptionCellDataSerde for CheckboxTypeOption { fn protobuf_encode( &self, cell_data: ::CellData, @@ -135,16 +136,32 @@ impl TypeOptionCellDataCompare for CheckboxTypeOption { &self, cell_data: &::CellData, other_cell_data: &::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, _: &::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<&::CellData>, + other_cell_data: Option<&::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, + } } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs index 3a3254a0ca..b03440540f 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checkbox_type_option/checkbox_type_option_entities.rs +++ b/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() diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs index 0b2f919380..a37aa48824 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist.rs +++ b/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 for TypeOptionData { } } -impl TypeOptionCellData for ChecklistTypeOption { +impl TypeOptionCellDataSerde for ChecklistTypeOption { fn protobuf_encode( &self, cell_data: ::CellData, @@ -191,6 +192,7 @@ impl TypeOptionCellDataCompare for ChecklistTypeOption { &self, cell_data: &::CellData, other_cell_data: &::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: &::CellData) -> bool { - cell_data.selected_option_ids.is_empty() - } } impl TypeOptionTransform for ChecklistTypeOption {} diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs index d11a25a502..95b22a0c75 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/checklist_type_option/checklist_entities.rs +++ b/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 { self diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs index 5e34a94d87..ea02c906f2 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_tests.rs +++ b/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] diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs index 74d3d8902d..94d145839c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option.rs +++ b/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 for TypeOptionData { } } -impl TypeOptionCellData for DateTypeOption { +impl TypeOptionCellDataSerde for DateTypeOption { fn protobuf_encode( &self, cell_data: ::CellData, @@ -306,16 +307,16 @@ impl TypeOptionCellDataCompare for DateTypeOption { &self, cell_data: &::CellData, other_cell_data: &::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: &::CellData) -> bool { - cell_data.timestamp.is_none() - } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs index d8aae585b5..1ca75b5457 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/date_type_option/date_type_option_entities.rs +++ b/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 diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs index 76f7422700..91afbdca93 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/number_type_option/number_type_option.rs +++ b/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 for TypeOptionData { } } -impl TypeOptionCellData for NumberTypeOption { +impl TypeOptionCellDataSerde for NumberTypeOption { fn protobuf_encode( &self, cell_data: ::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: &::CellData, other_cell_data: &::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: &::CellData) -> bool { - cell_data.0.is_empty() - } } + impl std::default::Default for NumberTypeOption { fn default() -> Self { let format = NumberFormat::default(); diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs index 797a108264..c37f1664fb 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/multi_select_type_option.rs +++ b/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 for TypeOptionData { } } -impl TypeOptionCellData for MultiSelectTypeOption { +impl TypeOptionCellDataSerde for MultiSelectTypeOption { fn protobuf_encode( &self, cell_data: ::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: &::CellData, other_cell_data: &::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(), - }; + 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; - } + if order.is_ne() { + return order; + } + } + default_order() + }, + order => sort_condition.evaluate_order(order), } - default_order() - } - - fn exempt_from_cmp(&self, cell_data: &::CellData) -> bool { - cell_data.is_empty() } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_ids.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_ids.rs index c9719d6f04..1914414b91 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_ids.rs +++ b/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 where diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs index 356d8d6d96..666ff41792 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/select_type_option.rs +++ b/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 CellDataDecoder for T where - T: SelectTypeOptionSharedAction + TypeOption + TypeOptionCellData, + T: + SelectTypeOptionSharedAction + TypeOption + TypeOptionCellDataSerde, { fn decode_cell( &self, diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs index 9b87d76ca2..c047922b3a 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/selection_type_option/single_select_type_option.rs +++ b/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 for TypeOptionData { } } -impl TypeOptionCellData for SingleSelectTypeOption { +impl TypeOptionCellDataSerde for SingleSelectTypeOption { fn protobuf_encode( &self, cell_data: ::CellData, @@ -131,6 +132,7 @@ impl TypeOptionCellDataCompare for SingleSelectTypeOption { &self, cell_data: &::CellData, other_cell_data: &::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: &::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()); } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs index 9f8dc5b8c3..aa161b137e 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/text_type_option/text_type_option.rs +++ b/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: ::CellData, @@ -152,12 +153,17 @@ impl TypeOptionCellDataCompare for RichTextTypeOption { &self, cell_data: &::CellData, other_cell_data: &::CellData, + sort_condition: SortCondition, ) -> Ordering { - cell_data.0.cmp(&other_cell_data.0) - } - - fn exempt_from_cmp(&self, cell_data: &::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()) diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs index 823d1f0854..7fcd10c0c9 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs +++ b/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<::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: &::CellData, other_cell_data: &::CellData, + sort_condition: SortCondition, ) -> Ordering; - fn exempt_from_cmp(&self, cell_data: &::CellData) -> bool; + /// Compares the two cells where one of the cells is None + fn apply_cmp_with_uninitialized( + &self, + cell_data: Option<&::CellData>, + other_cell_data: Option<&::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>( diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs index c668befec4..b9a2a276f7 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs +++ b/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: ::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) }, } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs index 7880bb0e82..37f93d8b66 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option.rs +++ b/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 for TypeOptionData { impl TypeOptionTransform for URLTypeOption {} -impl TypeOptionCellData for URLTypeOption { +impl TypeOptionCellDataSerde for URLTypeOption { fn protobuf_encode( &self, cell_data: ::CellData, @@ -123,12 +124,19 @@ impl TypeOptionCellDataCompare for URLTypeOption { &self, cell_data: &::CellData, other_cell_data: &::CellData, + sort_condition: SortCondition, ) -> Ordering { - cell_data.data.cmp(&other_cell_data.data) - } - - fn exempt_from_cmp(&self, cell_data: &::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) + }, + } } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option_entities.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option_entities.rs index c289668dab..442a6f062b 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/url_type_option/url_type_option_entities.rs +++ b/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(); diff --git a/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs b/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs index 77aa1df472..3540ba9c23 100644 --- a/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs +++ b/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_type: FieldType, cell_data_cache: &CellCache, @@ -306,6 +280,7 @@ fn cmp_cell( }, } } + #[derive(Serialize, Deserialize, Clone, Debug)] enum SortEvent { SortDidChanged, diff --git a/frontend/rust-lib/flowy-database2/src/services/sort/entities.rs b/frontend/rust-lib/flowy-database2/src/services/sort/entities.rs index 59e326b0b3..bfc550841c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/sort/entities.rs +++ b/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 { diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs index 38fa8ed254..dd30c75df6 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs +++ b/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 { diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs index 0c58c24d53..115ecd919e 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs +++ b/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 { diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs index 5597062dc0..c6cdef1db2 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs +++ b/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, diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs index 7f59f30a93..16c848ea12 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs +++ b/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 { diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs index 134d143ff1..517def3f8b 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs +++ b/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; } diff --git a/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs b/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs index 7555953d80..8355db8971 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/mock_data/grid_mock_data.rs +++ b/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"); + }, _ => {}, } diff --git a/frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs b/frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs index 88dafccade..be112c0c2f 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/share_test/export_test.rs +++ b/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); diff --git a/frontend/rust-lib/flowy-database2/tests/database/sort_test/checkbox_and_text_test.rs b/frontend/rust-lib/flowy-database2/tests/database/sort_test/checkbox_and_text_test.rs deleted file mode 100644 index c68d44e6b2..0000000000 --- a/frontend/rust-lib/flowy-database2/tests/database/sort_test/checkbox_and_text_test.rs +++ /dev/null @@ -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; -} diff --git a/frontend/rust-lib/flowy-database2/tests/database/sort_test/mod.rs b/frontend/rust-lib/flowy-database2/tests/database/sort_test/mod.rs index 69e0a622f8..d84b43ca7b 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/sort_test/mod.rs +++ b/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; diff --git a/frontend/rust-lib/flowy-database2/tests/database/sort_test/multi_sort_test.rs b/frontend/rust-lib/flowy-database2/tests/database/sort_test/multi_sort_test.rs index 3451b112ea..bd9ff68d47 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/sort_test/multi_sort_test.rs +++ b/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", ""], + orders: vec!["A", "", "C", "DA", "AE", "AE", "CB"], }, - AssertCellContentOrder { - field_id: checkbox_field.id.clone(), - orders: vec!["Yes", "No", "Yes", "No", "No", "Yes"], - }, - ]; - 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; diff --git a/frontend/rust-lib/flowy-database2/tests/database/sort_test/single_sort_test.rs b/frontend/rust-lib/flowy-database2/tests/database/sort_test/single_sort_test.rs index 39d1f0cf4b..209abb2e63 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/sort_test/single_sort_test.rs +++ b/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; diff --git a/frontend/rust-lib/flowy-folder2/src/entities/icon.rs b/frontend/rust-lib/flowy-folder2/src/entities/icon.rs index 6133bbe8da..08b8980209 100644 --- a/frontend/rust-lib/flowy-folder2/src/entities/icon.rs +++ b/frontend/rust-lib/flowy-folder2/src/entities/icon.rs @@ -21,9 +21,9 @@ impl std::convert::From for IconType { } } -impl Into for IconType { - fn into(self) -> ViewIconTypePB { - match self { +impl From 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 for ViewIcon { } } -impl Into for ViewIcon { - fn into(self) -> ViewIconPB { +impl From for ViewIconPB { + fn from(val: ViewIcon) -> Self { ViewIconPB { - ty: self.ty.into(), - value: self.value, + ty: val.ty.into(), + value: val.value, } } } diff --git a/frontend/rust-lib/flowy-test/src/lib.rs b/frontend/rust-lib/flowy-test/src/lib.rs index 4485c94259..a75d053cbc 100644 --- a/frontend/rust-lib/flowy-test/src/lib.rs +++ b/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()], ); diff --git a/frontend/rust-lib/flowy-test/tests/folder/local_test/subscription_test.rs b/frontend/rust-lib/flowy-test/tests/folder/local_test/subscription_test.rs index 4b7e167291..686d2b1384 100644 --- a/frontend/rust-lib/flowy-test/tests/folder/local_test/subscription_test.rs +++ b/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); } diff --git a/frontend/rust-lib/flowy-user/src/event_map.rs b/frontend/rust-lib/flowy-user/src/event_map.rs index 572eedeec3..5b1aadd84b 100644 --- a/frontend/rust-lib/flowy-user/src/event_map.rs +++ b/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) -> 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")