refactor: cell data transform logic (#5039)

* refactor: cell data transform logic

* chore: remove redundant select option event

* chore: adapt tests to changes
This commit is contained in:
Richard Shiue 2024-04-11 14:49:36 +08:00 committed by GitHub
parent 828f312294
commit b7b4ea2da1
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
37 changed files with 574 additions and 912 deletions

View File

@ -164,6 +164,39 @@ class SelectOptionCellEditorBloc
return super.close();
}
void _startListening() {
_onCellChangedFn = cellController.addListener(
onCellChanged: (_) {
_loadOptions();
},
onCellFieldChanged: (field) {
_loadOptions();
},
);
}
void _loadOptions() {
if (isClosed) {
Log.warn("Unexpecteded closing the bloc");
return;
}
final cellData = cellController.getCellData();
if (cellData != null) {
add(
SelectOptionCellEditorEvent.didReceiveOptions(
cellData.options,
cellData.selectOptions,
),
);
} else {
add(
const SelectOptionCellEditorEvent.didReceiveOptions([], []),
);
}
}
Future<void> _createOption({
required String name,
required SelectOptionColorPB color,
@ -251,27 +284,6 @@ class SelectOptionCellEditorBloc
);
}
Future<void> _loadOptions() async {
final result = await _selectOptionService.getCellData();
if (isClosed) {
Log.warn("Unexpecteded closing the bloc");
return;
}
return result.fold(
(data) => add(
SelectOptionCellEditorEvent.didReceiveOptions(
data.options,
data.selectOptions,
),
),
(err) {
Log.error(err);
return null;
},
);
}
_MakeOptionResult _getVisibleOptions(
List<SelectOptionPB> allOptions,
) {
@ -331,17 +343,6 @@ class SelectOptionCellEditorBloc
emit(state.copyWith(focusedOptionId: optionIds[newIndex]));
}
void _startListening() {
_onCellChangedFn = cellController.addListener(
onCellChanged: (selectOptionContext) {
_loadOptions();
},
onCellFieldChanged: (field) {
_loadOptions();
},
);
}
}
@freezed

View File

@ -60,15 +60,6 @@ class SelectOptionCellBackendService {
return DatabaseEventDeleteSelectOption(payload).send();
}
Future<FlowyResult<SelectOptionCellDataPB, FlowyError>> getCellData() {
final payload = CellIdPB()
..viewId = viewId
..fieldId = fieldId
..rowId = rowId;
return DatabaseEventGetSelectOptionCellData(payload).send();
}
Future<FlowyResult<void, FlowyError>> select({
required Iterable<String> optionIds,
}) {

View File

@ -91,12 +91,19 @@ void main() {
"Expect 3 but receive ${bloc.state.options.length}. Options: ${bloc.state.options}",
);
bloc.add(SelectOptionCellEditorEvent.deleteOption(bloc.state.options[0]));
await gridResponseFuture();
assert(
bloc.state.options.length == 2,
"Expect 2 but receive ${bloc.state.options.length}. Options: ${bloc.state.options}",
);
bloc.add(const SelectOptionCellEditorEvent.deleteAllOptions());
await gridResponseFuture();
assert(
bloc.state.options.isEmpty,
"Expect empty but receive ${bloc.state.options.length}",
"Expect empty but receive ${bloc.state.options.length}. Options: ${bloc.state.options}",
);
});

View File

@ -532,20 +532,6 @@ pub(crate) async fn delete_select_option_handler(
Ok(())
}
#[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn get_select_option_handler(
data: AFPluginData<CellIdPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<SelectOptionCellDataPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let params: CellIdParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let options = database_editor
.get_select_options(params.row_id, &params.field_id)
.await;
data_result_ok(options)
}
#[tracing::instrument(level = "trace", skip_all, err)]
pub(crate) async fn update_select_option_cell_handler(
data: AFPluginData<SelectOptionCellChangesetPB>,

View File

@ -47,7 +47,6 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::CreateSelectOption, new_select_option_handler)
.event(DatabaseEvent::InsertOrUpdateSelectOption, insert_or_update_select_option_handler)
.event(DatabaseEvent::DeleteSelectOption, delete_select_option_handler)
.event(DatabaseEvent::GetSelectOptionCellData, get_select_option_handler)
.event(DatabaseEvent::UpdateSelectOptionCell, update_select_option_cell_handler)
// Checklist
.event(DatabaseEvent::UpdateChecklistCell, update_checklist_cell_handler)
@ -201,12 +200,6 @@ pub enum DatabaseEvent {
#[event(input = "CreateSelectOptionPayloadPB", output = "SelectOptionPB")]
CreateSelectOption = 30,
/// [GetSelectOptionCellData] event is used to get the select option data for cell editing.
/// [CellIdPB] locate which cell data that will be read from. The return value, [SelectOptionCellDataPB]
/// contains the available options and the currently selected options.
#[event(input = "CellIdPB", output = "SelectOptionCellDataPB")]
GetSelectOptionCellData = 31,
/// [InsertOrUpdateSelectOption] event is used to update a FieldTypeOptionData whose field_type is
/// FieldType::SingleSelect or FieldType::MultiSelect.
///
@ -214,10 +207,10 @@ pub enum DatabaseEvent {
/// For example, DatabaseNotification::DidUpdateCell will be triggered if the [SelectOptionChangesetPB]
/// carries a change that updates the name of the option.
#[event(input = "RepeatedSelectOptionPayload")]
InsertOrUpdateSelectOption = 32,
InsertOrUpdateSelectOption = 31,
#[event(input = "RepeatedSelectOptionPayload")]
DeleteSelectOption = 33,
DeleteSelectOption = 32,
#[event(input = "CreateRowPayloadPB", output = "RowMetaPB")]
CreateRow = 50,

View File

@ -1,9 +1,10 @@
use crate::entities::{CalculationType, FieldType};
use std::sync::Arc;
use crate::services::field::TypeOptionCellExt;
use collab_database::fields::Field;
use collab_database::rows::RowCell;
use std::sync::Arc;
use crate::entities::CalculationType;
use crate::services::field::TypeOptionCellExt;
pub struct CalculationsService {}
@ -35,10 +36,7 @@ impl CalculationsService {
fn calculate_average(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
let mut sum = 0.0;
let mut len = 0.0;
let field_type = FieldType::from(field.field_type);
if let Some(handler) =
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
{
if let Some(handler) = TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
for row_cell in row_cells {
if let Some(cell) = &row_cell.cell {
if let Some(value) = handler.handle_numeric_cell(cell) {
@ -130,50 +128,39 @@ impl CalculationsService {
}
fn calculate_count_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
let field_type = FieldType::from(field.field_type);
if let Some(handler) =
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
{
if !row_cells.is_empty() {
return format!(
"{}",
row_cells
.iter()
.filter(|c| c.is_none()
|| handler
.handle_stringify_cell(&c.cell.clone().unwrap_or_default(), &field_type, field)
.is_empty())
.collect::<Vec<_>>()
.len()
);
}
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
Some(handler) if !row_cells.is_empty() => row_cells
.iter()
.filter(|row_cell| {
if let Some(cell) = &row_cell.cell {
handler.handle_is_cell_empty(cell, field)
} else {
true
}
})
.collect::<Vec<_>>()
.len()
.to_string(),
_ => "".to_string(),
}
String::new()
}
fn calculate_count_non_empty(&self, field: &Field, row_cells: Vec<Arc<RowCell>>) -> String {
let field_type = FieldType::from(field.field_type);
if let Some(handler) =
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
{
if !row_cells.is_empty() {
return format!(
"{}",
row_cells
.iter()
// Check the Cell has data and that the stringified version is not empty
.filter(|c| c.is_some()
&& !handler
.handle_stringify_cell(&c.cell.clone().unwrap_or_default(), &field_type, field)
.is_empty())
.collect::<Vec<_>>()
.len()
);
}
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
Some(handler) if !row_cells.is_empty() => row_cells
.iter()
.filter(|row_cell| {
if let Some(cell) = &row_cell.cell {
!handler.handle_is_cell_empty(cell, field)
} else {
false
}
})
.collect::<Vec<_>>()
.len()
.to_string(),
_ => "".to_string(),
}
String::new()
}
fn reduce_values_f64<F, T>(&self, field: &Field, row_cells: Vec<Arc<RowCell>>, f: F) -> T
@ -182,10 +169,7 @@ impl CalculationsService {
{
let mut values = vec![];
let field_type = FieldType::from(field.field_type);
if let Some(handler) =
TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type)
{
if let Some(handler) = TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler() {
for row_cell in row_cells {
if let Some(cell) = &row_cell.cell {
if let Some(value) = handler.handle_numeric_cell(cell) {

View File

@ -14,7 +14,6 @@ use crate::services::group::make_no_status_group;
/// Decode the opaque cell data into readable format content
pub trait CellDataDecoder: TypeOption {
///
/// Tries to decode the [Cell] to `decoded_field_type`'s cell data. Sometimes, the `field_type`
/// of the `Field` is not equal to the `decoded_field_type`(This happened When switching
/// the field type of the `Field` to another field type). So the cell data is need to do
@ -25,21 +24,30 @@ pub trait CellDataDecoder: TypeOption {
/// But the data of the cell doesn't change. We can't iterate all the rows to transform the cell
/// data that can be parsed by the current field type. One approach is to transform the cell data
/// when reading.
fn decode_cell(
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData>;
/// Transform the cell data from one field type to another
///
/// # Arguments
///
/// * `cell`: the cell in the current field type
/// * `transformed_field_type`: the cell will be transformed to the is field type's cell data.
/// current `TypeOption` field type.
///
fn decode_cell_with_transform(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData>;
_cell: &Cell,
_from_field_type: FieldType,
_field: &Field,
) -> Option<<Self as TypeOption>::CellData> {
None
}
/// Decode the cell data to readable `String`
/// For example, The string of the Multi-Select cell will be a list of the option's name
/// separated by a comma.
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String;
/// Same as [CellDataDecoder::stringify_cell_data] but the input parameter is the [Cell]
fn stringify_cell(&self, cell: &Cell) -> String;
/// Decode the cell into f64
/// Different field type has different way to decode the cell data into f64
/// If the field type doesn't support to decode the cell data into f64, it will return None
@ -74,10 +82,7 @@ pub fn apply_cell_changeset(
field: &Field,
cell_data_cache: Option<CellCache>,
) -> Result<Cell, FlowyError> {
let field_type = FieldType::from(field.field_type);
match TypeOptionCellExt::new(field, cell_data_cache)
.get_type_option_cell_data_handler(&field_type)
{
match TypeOptionCellExt::new(field, cell_data_cache).get_type_option_cell_data_handler() {
None => Ok(Cell::default()),
Some(handler) => Ok(handler.handle_cell_changeset(changeset, cell, field)?),
}
@ -88,15 +93,7 @@ pub fn get_cell_protobuf(
field: &Field,
cell_cache: Option<CellCache>,
) -> CellProtobufBlob {
let from_field_type = get_field_type_from_cell(cell);
if from_field_type.is_none() {
return CellProtobufBlob::default();
}
let from_field_type = from_field_type.unwrap();
let to_field_type = FieldType::from(field.field_type);
match try_decode_cell_to_cell_protobuf(cell, &from_field_type, &to_field_type, field, cell_cache)
{
match try_decode_cell_to_cell_protobuf(cell, field, cell_cache) {
Ok(cell_bytes) => cell_bytes,
Err(e) => {
tracing::error!("Decode cell data failed, {:?}", e);
@ -123,34 +120,15 @@ pub fn get_cell_protobuf(
///
pub fn try_decode_cell_to_cell_protobuf(
cell: &Cell,
from_field_type: &FieldType,
to_field_type: &FieldType,
field: &Field,
cell_data_cache: Option<CellCache>,
) -> FlowyResult<CellProtobufBlob> {
match TypeOptionCellExt::new(field, cell_data_cache)
.get_type_option_cell_data_handler(to_field_type)
{
match TypeOptionCellExt::new(field, cell_data_cache).get_type_option_cell_data_handler() {
None => Ok(CellProtobufBlob::default()),
Some(handler) => handler.handle_cell_protobuf(cell, from_field_type, field),
Some(handler) => handler.handle_get_protobuf_cell_data(cell, field),
}
}
pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
cell: &Cell,
from_field_type: &FieldType,
to_field_type: &FieldType,
field: &Field,
cell_data_cache: Option<CellCache>,
) -> Option<T> {
let handler = TypeOptionCellExt::new(field, cell_data_cache)
.get_type_option_cell_data_handler(to_field_type)?;
handler
.get_cell_data(cell, from_field_type, field)
.ok()?
.unbox_or_none::<T>()
}
/// Returns a string that represents the current field_type's cell data.
/// For example, a Multi-Select cell will be represented by a list of the options' names
/// separated by commas.
@ -162,15 +140,14 @@ pub fn try_decode_cell_to_cell_data<T: Default + 'static>(
/// * `from_field_type`: the original field type of the passed-in cell data.
/// * `field`: used to get the corresponding TypeOption for the specified field type.
///
pub fn stringify_cell_data(
cell: &Cell,
to_field_type: &FieldType,
from_field_type: &FieldType,
field: &Field,
) -> String {
match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(from_field_type) {
None => "".to_string(),
Some(handler) => handler.handle_stringify_cell(cell, to_field_type, field),
pub fn stringify_cell(cell: &Cell, field: &Field) -> String {
if let Some(field_type_of_cell) = get_field_type_from_cell::<FieldType>(cell) {
TypeOptionCellExt::new(field, None)
.get_type_option_cell_data_handler_with_field_type(field_type_of_cell)
.map(|handler| handler.handle_stringify_cell(cell, field))
.unwrap_or_default()
} else {
"".to_string()
}
}

View File

@ -10,7 +10,7 @@ use crate::services::database_view::{
use crate::services::field::{
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
type_option_data_from_pb, ChecklistCellChangeset, RelationTypeOption, SelectOptionCellChangeset,
SelectOptionIds, StrCellData, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
StrCellData, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
};
use crate::services::field_settings::{
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
@ -406,16 +406,16 @@ impl DatabaseEditor {
}
let old_field_type = FieldType::from(field.field_type);
let old_type_option = field.get_any_type_option(old_field_type);
let new_type_option = field
let old_type_option_data = field.get_any_type_option(old_field_type);
let new_type_option_data = field
.get_any_type_option(new_field_type)
.unwrap_or_else(|| default_type_option_data_from_type(new_field_type));
let transformed_type_option = transform_type_option(
&new_type_option,
new_field_type,
old_type_option,
old_field_type,
new_field_type,
old_type_option_data,
new_type_option_data,
);
self
.database
@ -1015,24 +1015,6 @@ impl DatabaseEditor {
Ok(())
}
pub async fn get_select_options(&self, row_id: RowId, field_id: &str) -> SelectOptionCellDataPB {
let field = self.database.lock().fields.get_field(field_id);
match field {
None => SelectOptionCellDataPB::default(),
Some(field) => {
let cell = self.database.lock().get_cell(field_id, &row_id).cell;
let ids = match cell {
None => SelectOptionIds::new(),
Some(cell) => SelectOptionIds::from(&cell),
};
match select_type_option_from_field(&field) {
Ok(type_option) => type_option.get_selected_options(ids).into(),
Err(_) => SelectOptionCellDataPB::default(),
}
},
}
}
pub async fn set_checklist_options(
&self,
view_id: &str,
@ -1349,7 +1331,7 @@ impl DatabaseEditor {
) -> FlowyResult<Vec<RelatedRowDataPB>> {
let primary_field = self.database.lock().fields.get_primary_field().unwrap();
let handler = TypeOptionCellExt::new(&primary_field, Some(self.cell_cache.clone()))
.get_type_option_cell_data_handler(&FieldType::RichText)
.get_type_option_cell_data_handler_with_field_type(FieldType::RichText)
.ok_or(FlowyError::internal())?;
let row_data = {
@ -1364,11 +1346,7 @@ impl DatabaseEditor {
let title = database
.get_cell(&primary_field.id, &row.id)
.cell
.and_then(|cell| {
handler
.get_cell_data(&cell, &FieldType::RichText, &primary_field)
.ok()
})
.and_then(|cell| handler.handle_get_boxed_cell_data(&cell, &primary_field))
.and_then(|cell_data| cell_data.unbox_or_none())
.unwrap_or_else(|| StrCellData("".to_string()));
@ -1715,10 +1693,8 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl {
fn get_type_option_cell_handler(
&self,
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
TypeOptionCellExt::new(field, Some(self.cell_cache.clone()))
.get_type_option_cell_data_handler(field_type)
TypeOptionCellExt::new(field, Some(self.cell_cache.clone())).get_type_option_cell_data_handler()
}
fn get_field_settings(

View File

@ -111,11 +111,11 @@ pub(crate) async fn get_cell_for_row(
let field = delegate.get_field(field_id)?;
let row_cell = delegate.get_cell_in_row(field_id, row_id).await;
let field_type = FieldType::from(field.field_type);
let handler = delegate.get_type_option_cell_handler(&field, &field_type)?;
let handler = delegate.get_type_option_cell_handler(&field)?;
let cell_data = match &row_cell.cell {
None => None,
Some(cell) => handler.get_cell_data(cell, &field_type, &field).ok(),
Some(cell) => handler.handle_get_boxed_cell_data(cell, &field),
};
Some(RowSingleCellData {
row_id: row_cell.row_id.clone(),
@ -133,14 +133,14 @@ pub(crate) async fn get_cells_for_field(
) -> Vec<RowSingleCellData> {
if let Some(field) = delegate.get_field(field_id) {
let field_type = FieldType::from(field.field_type);
if let Some(handler) = delegate.get_type_option_cell_handler(&field, &field_type) {
if let Some(handler) = delegate.get_type_option_cell_handler(&field) {
let cells = delegate.get_cells_for_field(view_id, field_id).await;
return cells
.iter()
.map(|row_cell| {
let cell_data = match &row_cell.cell {
None => None,
Some(cell) => handler.get_cell_data(cell, &field_type, &field).ok(),
Some(cell) => handler.handle_get_boxed_cell_data(cell, &field),
};
RowSingleCellData {
row_id: row_cell.row_id.clone(),

View File

@ -118,7 +118,6 @@ pub trait DatabaseViewOperation: Send + Sync + 'static {
fn get_type_option_cell_handler(
&self,
field: &Field,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
fn get_field_settings(

View File

@ -1,14 +1,12 @@
use std::sync::Arc;
use collab_database::fields::TypeOptionData;
use flowy_error::FlowyResult;
use crate::entities::FieldType;
use crate::services::database::DatabaseEditor;
use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption};
use crate::services::field::{MultiSelectTypeOption, SingleSelectTypeOption, TypeOption};
pub async fn edit_field_type_option<T: From<TypeOptionData> + Into<TypeOptionData>>(
pub async fn edit_field_type_option<T: TypeOption>(
field_id: &str,
editor: Arc<DatabaseEditor>,
action: impl FnOnce(&mut T),
@ -22,7 +20,7 @@ pub async fn edit_field_type_option<T: From<TypeOptionData> + Into<TypeOptionDat
if let Some(mut type_option) = get_type_option.await {
if let Some(old_field) = editor.get_field(field_id) {
action(&mut type_option);
let type_option_data: TypeOptionData = type_option.into();
let type_option_data = type_option.into();
editor
.update_field_type_option(field_id, type_option_data, old_field)
.await?;

View File

@ -38,16 +38,12 @@ mod tests {
type_option: &CheckboxTypeOption,
input_str: &str,
expected_str: &str,
field_type: &FieldType,
field: &Field,
_field_type: &FieldType,
_field: &Field,
) {
assert_eq!(
type_option
.decode_cell(
&CheckboxCellDataPB::from_str(input_str).unwrap().into(),
field_type,
field
)
.decode_cell(&CheckboxCellDataPB::from_str(input_str).unwrap().into())
.unwrap()
.to_string(),
expected_str.to_owned()

View File

@ -25,31 +25,7 @@ impl TypeOption for CheckboxTypeOption {
type CellFilter = CheckboxFilterPB;
}
impl TypeOptionTransform for CheckboxTypeOption {
fn transformable(&self) -> bool {
true
}
fn transform_type_option(
&mut self,
_old_type_option_field_type: FieldType,
_old_type_option_data: TypeOptionData,
) {
}
fn transform_type_option_cell(
&self,
cell: &Cell,
transformed_field_type: &FieldType,
_field: &Field,
) -> Option<<Self as TypeOption>::CellData> {
if transformed_field_type.is_text() {
Some(CheckboxCellDataPB::from(cell))
} else {
None
}
}
}
impl TypeOptionTransform for CheckboxTypeOption {}
impl From<TypeOptionData> for CheckboxTypeOption {
fn from(_data: TypeOptionData) -> Self {
@ -77,26 +53,27 @@ impl TypeOptionCellDataSerde for CheckboxTypeOption {
}
impl CellDataDecoder for CheckboxTypeOption {
fn decode_cell(
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
fn decode_cell_with_transform(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
from_field_type: FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if !decoded_field_type.is_checkbox() {
return Ok(Default::default());
) -> Option<<Self as TypeOption>::CellData> {
if from_field_type.is_text() {
Some(CheckboxCellDataPB::from(cell))
} else {
None
}
self.parse_cell(cell)
}
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.to_string()
}
fn stringify_cell(&self, cell: &Cell) -> String {
Self::CellData::from(cell).to_string()
}
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
let cell_data = self.parse_cell(cell).ok()?;
if cell_data.is_checked {

View File

@ -1,4 +1,10 @@
use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, FieldType, SelectOptionPB};
use std::cmp::Ordering;
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use flowy_error::FlowyResult;
use crate::entities::{ChecklistCellDataPB, ChecklistFilterPB, SelectOptionPB};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::checklist_type_option::{ChecklistCellChangeset, ChecklistCellData};
use crate::services::field::{
@ -6,10 +12,6 @@ use crate::services::field::{
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;
use std::cmp::Ordering;
#[derive(Debug, Clone, Default)]
pub struct ChecklistTypeOption;
@ -141,16 +143,7 @@ fn update_cell_data_with_changeset(
}
impl CellDataDecoder for ChecklistTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if !decoded_field_type.is_checklist() {
return Ok(Default::default());
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
@ -163,11 +156,6 @@ impl CellDataDecoder for ChecklistTypeOption {
.join(SELECTION_IDS_SEPARATOR)
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = self.parse_cell(cell).unwrap_or_default();
self.stringify_cell_data(cell_data)
}
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
// return the percentage complete if needed
None

View File

@ -546,10 +546,8 @@ mod tests {
assert_eq!(decode_cell_data(&cell, type_option, field), expected_str,);
}
fn decode_cell_data(cell: &Cell, type_option: &DateTypeOption, field: &Field) -> String {
let decoded_data = type_option
.decode_cell(cell, &FieldType::DateTime, field)
.unwrap();
fn decode_cell_data(cell: &Cell, type_option: &DateTypeOption, _field: &Field) -> String {
let decoded_data = type_option.decode_cell(cell).unwrap();
type_option.stringify_cell_data(decoded_data)
}

View File

@ -4,13 +4,13 @@ use std::str::FromStr;
use chrono::{DateTime, FixedOffset, Local, NaiveDateTime, NaiveTime, Offset, TimeZone};
use chrono_tz::Tz;
use collab::core::any_map::AnyMapExtension;
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use serde::{Deserialize, Serialize};
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use crate::entities::{DateCellDataPB, DateFilterPB, FieldType};
use crate::entities::{DateCellDataPB, DateFilterPB};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::{
default_order, DateCellChangeset, DateCellData, DateFormat, TimeFormat, TypeOption,
@ -199,20 +199,7 @@ impl DateTypeOption {
impl TypeOptionTransform for DateTypeOption {}
impl CellDataDecoder for DateTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
// Return default data if the type_option_cell_data is not FieldType::DateTime.
// It happens when switching from one field to another.
// For example:
// FieldType::RichText -> FieldType::DateTime, it will display empty content on the screen.
if !decoded_field_type.is_date() {
return Ok(Default::default());
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
@ -244,11 +231,6 @@ impl CellDataDecoder for DateTypeOption {
}
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.stringify_cell_data(cell_data)
}
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
None
}

View File

@ -1,111 +1,77 @@
#[cfg(test)]
mod tests {
use collab_database::fields::Field;
use crate::entities::FieldType;
use crate::services::cell::CellDataDecoder;
use crate::services::field::{FieldBuilder, NumberCellData};
use crate::services::field::NumberCellData;
use crate::services::field::{NumberFormat, NumberTypeOption};
/// Testing when the input is not a number.
#[test]
fn number_type_option_input_test() {
let type_option = NumberTypeOption::default();
let field_type = FieldType::Number;
let field = FieldBuilder::from_field_type(field_type).build();
// Input is empty String
assert_number(&type_option, "", "", &field_type, &field);
assert_number(&type_option, "abc", "", &field_type, &field);
assert_number(&type_option, "-123", "-123", &field_type, &field);
assert_number(&type_option, "abc-123", "-123", &field_type, &field);
assert_number(&type_option, "+123", "123", &field_type, &field);
assert_number(&type_option, "0.2", "0.2", &field_type, &field);
assert_number(&type_option, "-0.2", "-0.2", &field_type, &field);
assert_number(&type_option, "-$0.2", "0.2", &field_type, &field);
assert_number(&type_option, ".2", "0.2", &field_type, &field);
assert_number(&type_option, "", "");
assert_number(&type_option, "abc", "");
assert_number(&type_option, "-123", "-123");
assert_number(&type_option, "abc-123", "-123");
assert_number(&type_option, "+123", "123");
assert_number(&type_option, "0.2", "0.2");
assert_number(&type_option, "-0.2", "-0.2");
assert_number(&type_option, "-$0.2", "0.2");
assert_number(&type_option, ".2", "0.2");
}
#[test]
fn dollar_type_option_test() {
let field_type = FieldType::Number;
let mut type_option = NumberTypeOption::new();
type_option.format = NumberFormat::USD;
let field = FieldBuilder::new(field_type, type_option.clone()).build();
assert_number(&type_option, "", "", &field_type, &field);
assert_number(&type_option, "abc", "", &field_type, &field);
assert_number(&type_option, "-123", "-$123", &field_type, &field);
assert_number(&type_option, "+123", "$123", &field_type, &field);
assert_number(&type_option, "0.2", "$0.2", &field_type, &field);
assert_number(&type_option, "-0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, "-$0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, ".2", "$0.2", &field_type, &field);
assert_number(&type_option, "", "");
assert_number(&type_option, "abc", "");
assert_number(&type_option, "-123", "-$123");
assert_number(&type_option, "+123", "$123");
assert_number(&type_option, "0.2", "$0.2");
assert_number(&type_option, "-0.2", "-$0.2");
assert_number(&type_option, "-$0.2", "-$0.2");
assert_number(&type_option, "-€0.2", "-$0.2");
assert_number(&type_option, ".2", "$0.2");
}
#[test]
fn dollar_type_option_test2() {
let field_type = FieldType::Number;
let mut type_option = NumberTypeOption::new();
type_option.format = NumberFormat::USD;
let field = FieldBuilder::new(field_type, type_option.clone()).build();
assert_number(
&type_option,
"99999999999",
"$99,999,999,999",
&field_type,
&field,
);
assert_number(
&type_option,
"$99,999,999,999",
"$99,999,999,999",
&field_type,
&field,
);
assert_number(&type_option, "99999999999", "$99,999,999,999");
assert_number(&type_option, "$99,999,999,999", "$99,999,999,999");
}
#[test]
fn other_symbol_to_dollar_type_option_test() {
let field_type = FieldType::Number;
let mut type_option = NumberTypeOption::new();
type_option.format = NumberFormat::USD;
let field = FieldBuilder::new(field_type, type_option.clone()).build();
assert_number(&type_option, "€0.2", "$0.2", &field_type, &field);
assert_number(&type_option, "-€0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, "-CN¥0.2", "-$0.2", &field_type, &field);
assert_number(&type_option, "CN¥0.2", "$0.2", &field_type, &field);
assert_number(&type_option, "0.2", "$0.2", &field_type, &field);
assert_number(&type_option, "€0.2", "$0.2");
assert_number(&type_option, "-€0.2", "-$0.2");
assert_number(&type_option, "-CN¥0.2", "-$0.2");
assert_number(&type_option, "CN¥0.2", "$0.2");
assert_number(&type_option, "0.2", "$0.2");
}
#[test]
fn euro_type_option_test() {
let field_type = FieldType::Number;
let mut type_option = NumberTypeOption::new();
type_option.format = NumberFormat::EUR;
let field = FieldBuilder::new(field_type, type_option.clone()).build();
assert_number(&type_option, "0.2", "€0,2", &field_type, &field);
assert_number(&type_option, "1000", "€1.000", &field_type, &field);
assert_number(&type_option, "1234.56", "€1.234,56", &field_type, &field);
assert_number(&type_option, "0.2", "€0,2");
assert_number(&type_option, "1000", "€1.000");
assert_number(&type_option, "1234.56", "€1.234,56");
}
fn assert_number(
type_option: &NumberTypeOption,
input_str: &str,
expected_str: &str,
field_type: &FieldType,
field: &Field,
) {
fn assert_number(type_option: &NumberTypeOption, input_str: &str, expected_str: &str) {
assert_eq!(
type_option
.decode_cell(
&NumberCellData(input_str.to_owned()).into(),
field_type,
field
)
.decode_cell(&NumberCellData(input_str.to_owned()).into(),)
.unwrap()
.to_string(),
expected_str.to_owned()

View File

@ -3,7 +3,7 @@ use std::default::Default;
use std::str::FromStr;
use collab::core::any_map::AnyMapExtension;
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::{new_cell_builder, Cell};
use fancy_regex::Regex;
use lazy_static::lazy_static;
@ -175,19 +175,7 @@ impl NumberTypeOption {
impl TypeOptionTransform for NumberTypeOption {}
impl CellDataDecoder for NumberTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if decoded_field_type.is_date()
|| decoded_field_type.is_created_time()
|| decoded_field_type.is_last_edited_time()
{
return Ok(Default::default());
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
let num_cell_data = self.parse_cell(cell)?;
Ok(NumberCellData::from(
self.format_cell_data(&num_cell_data)?.to_string(),
@ -201,11 +189,6 @@ impl CellDataDecoder for NumberTypeOption {
}
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.stringify_cell_data(cell_data)
}
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
let num_cell_data = self.parse_cell(cell).ok()?;
num_cell_data.0.parse::<f64>().ok()

View File

@ -1,12 +1,12 @@
use std::cmp::Ordering;
use collab::core::any_map::AnyMapExtension;
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use flowy_error::FlowyResult;
use serde::{Deserialize, Serialize};
use crate::entities::{FieldType, RelationCellDataPB, RelationFilterPB};
use crate::entities::{RelationCellDataPB, RelationFilterPB};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::{
default_order, TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter,
@ -77,16 +77,7 @@ impl CellDataChangeset for RelationTypeOption {
}
impl CellDataDecoder for RelationTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<RelationCellData> {
if !decoded_field_type.is_relation() {
return Ok(Default::default());
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<RelationCellData> {
Ok(cell.into())
}
@ -94,11 +85,6 @@ impl CellDataDecoder for RelationTypeOption {
cell_data.to_string()
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = RelationCellData::from(cell);
cell_data.to_string()
}
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
None
}
@ -121,27 +107,7 @@ impl TypeOptionCellDataFilter for RelationTypeOption {
}
}
impl TypeOptionTransform for RelationTypeOption {
fn transformable(&self) -> bool {
false
}
fn transform_type_option(
&mut self,
_old_type_option_field_type: FieldType,
_old_type_option_data: TypeOptionData,
) {
}
fn transform_type_option_cell(
&self,
_cell: &Cell,
_transformed_field_type: &FieldType,
_field: &Field,
) -> Option<RelationCellData> {
None
}
}
impl TypeOptionTransform for RelationTypeOption {}
impl TypeOptionCellDataSerde for RelationTypeOption {
fn protobuf_encode(&self, cell_data: RelationCellData) -> RelationCellDataPB {

View File

@ -75,10 +75,6 @@ impl<T> TypeOptionTransform for T
where
T: SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + CellDataDecoder,
{
fn transformable(&self) -> bool {
true
}
fn transform_type_option(
&mut self,
_old_type_option_field_type: FieldType,
@ -90,18 +86,24 @@ where
_old_type_option_data,
);
}
}
fn transform_type_option_cell(
impl<T> CellDataDecoder for T
where
T:
SelectTypeOptionSharedAction + TypeOption<CellData = SelectOptionIds> + TypeOptionCellDataSerde,
{
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
fn decode_cell_with_transform(
&self,
cell: &Cell,
transformed_field_type: &FieldType,
from_field_type: FieldType,
_field: &Field,
) -> Option<<Self as TypeOption>::CellData> {
match transformed_field_type {
FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => {
// If the transformed field type is SingleSelect, MultiSelect or Checklist, Do nothing.
None
},
match from_field_type {
FieldType::Checkbox => {
let cell_content = CheckboxCellDataPB::from(cell).to_string();
let mut transformed_ids = Vec::new();
@ -112,28 +114,14 @@ where
Some(SelectOptionIds::from(transformed_ids))
},
FieldType::RichText => Some(SelectOptionIds::from(cell)),
_ => Some(SelectOptionIds::from(vec![])),
FieldType::SingleSelect | FieldType::MultiSelect => Some(SelectOptionIds::from(cell)),
_ => None,
}
}
}
impl<T, C> CellDataDecoder for T
where
C: Into<SelectOptionIds> + for<'a> From<&'a Cell>,
T: SelectTypeOptionSharedAction + TypeOption<CellData = C> + TypeOptionCellDataSerde,
{
fn decode_cell(
&self,
cell: &Cell,
_decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
fn stringify_cell_data(&self, cell_data: C) -> String {
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
self
.get_selected_options(cell_data.into())
.get_selected_options(cell_data)
.select_options
.into_iter()
.map(|option| option.name)
@ -141,11 +129,6 @@ where
.join(SELECTION_IDS_SEPARATOR)
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = C::from(cell);
self.stringify_cell_data(cell_data)
}
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
None
}

View File

@ -23,7 +23,7 @@ impl SelectOptionTypeOptionTransformHelper {
{
match old_field_type {
FieldType::Checkbox => {
//add Yes and No options if it does not exist.
// add Yes and No options if it does not exist.
if !shared.options().iter().any(|option| option.name == CHECK) {
let check_option = SelectOption::with_color(CHECK, SelectOptionColor::Green);
shared.mut_options().push(check_option);

View File

@ -1,9 +1,7 @@
#[cfg(test)]
mod tests {
use collab_database::rows::Cell;
use crate::entities::FieldType;
use crate::services::cell::stringify_cell_data;
use crate::services::cell::{insert_select_option_cell, stringify_cell};
use crate::services::field::FieldBuilder;
use crate::services::field::*;
@ -14,15 +12,12 @@ mod tests {
let field_type = FieldType::DateTime;
let field = FieldBuilder::new(field_type, DateTypeOption::test()).build();
assert_eq!(
stringify_cell_data(
&to_text_cell(1647251762.to_string()),
&FieldType::RichText,
&field_type,
&field
),
"Mar 14, 2022"
);
let data = DateCellData {
timestamp: Some(1647251762),
..Default::default()
};
assert_eq!(stringify_cell(&(&data).into(), &field), "Mar 14, 2022");
let data = DateCellData {
timestamp: Some(1647251762),
@ -33,7 +28,7 @@ mod tests {
};
assert_eq!(
stringify_cell_data(&(&data).into(), &FieldType::RichText, &field_type, &field),
stringify_cell(&(&data).into(), &field),
"Mar 14, 2022 09:56"
);
@ -46,7 +41,7 @@ mod tests {
};
assert_eq!(
stringify_cell_data(&(&data).into(), &FieldType::RichText, &field_type, &field),
stringify_cell(&(&data).into(), &field),
"Mar 14, 2022 09:56"
);
@ -59,15 +54,11 @@ mod tests {
};
assert_eq!(
stringify_cell_data(&(&data).into(), &FieldType::RichText, &field_type, &field),
stringify_cell(&(&data).into(), &field),
"Mar 14, 2022 09:56 → Mar 29, 2022 06:03"
);
}
fn to_text_cell(s: String) -> Cell {
StrCellData(s).into()
}
// Test parser the cell data which field's type is FieldType::SingleSelect to cell data
// which field's type is FieldType::Text
#[test]
@ -82,20 +73,11 @@ mod tests {
};
let field = FieldBuilder::new(field_type, single_select).build();
assert_eq!(
stringify_cell_data(
&to_text_cell(option_id),
&FieldType::RichText,
&field_type,
&field
),
done_option.name,
);
let cell = insert_select_option_cell(vec![option_id], &field);
assert_eq!(stringify_cell(&cell, &field), done_option.name,);
}
/*
- [Unit Test] Testing the switching from Multi-selection type to Text type
- Tracking : https://github.com/AppFlowy-IO/AppFlowy/issues/1183
*/
#[test]
fn multiselect_to_text_type() {
let field_type = FieldType::MultiSelect;
@ -110,15 +92,12 @@ mod tests {
let france_option_id = france.id;
let argentina_option_id = argentina.id;
let field_rev = FieldBuilder::new(field_type, multi_select).build();
let field = FieldBuilder::new(field_type, multi_select).build();
let cell = insert_select_option_cell(vec![france_option_id, argentina_option_id], &field);
assert_eq!(
stringify_cell_data(
&to_text_cell(format!("{},{}", france_option_id, argentina_option_id)),
&FieldType::RichText,
&field_type,
&field_rev
),
stringify_cell(&cell, &field),
format!("{},{}", france.name, argentina.name)
);
}

View File

@ -10,7 +10,7 @@ use flowy_error::{FlowyError, FlowyResult};
use crate::entities::{FieldType, TextFilterPB};
use crate::services::cell::{
stringify_cell_data, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser,
stringify_cell, CellDataChangeset, CellDataDecoder, CellProtobufBlobParser,
};
use crate::services::field::type_options::util::ProtobufStr;
use crate::services::field::{
@ -49,42 +49,7 @@ impl From<RichTextTypeOption> for TypeOptionData {
}
}
impl TypeOptionTransform for RichTextTypeOption {
fn transformable(&self) -> bool {
true
}
fn transform_type_option(
&mut self,
_old_type_option_field_type: FieldType,
_old_type_option_data: TypeOptionData,
) {
}
fn transform_type_option_cell(
&self,
cell: &Cell,
transformed_field_type: &FieldType,
_field: &Field,
) -> Option<<Self as TypeOption>::CellData> {
if transformed_field_type.is_date()
|| transformed_field_type.is_single_select()
|| transformed_field_type.is_multi_select()
|| transformed_field_type.is_number()
|| transformed_field_type.is_url()
|| transformed_field_type.is_checklist()
{
Some(StrCellData::from(stringify_cell_data(
cell,
transformed_field_type,
transformed_field_type,
_field,
)))
} else {
Some(StrCellData::from(cell))
}
}
}
impl TypeOptionTransform for RichTextTypeOption {}
impl TypeOptionCellDataSerde for RichTextTypeOption {
fn protobuf_encode(
@ -100,23 +65,35 @@ impl TypeOptionCellDataSerde for RichTextTypeOption {
}
impl CellDataDecoder for RichTextTypeOption {
fn decode_cell(
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(StrCellData::from(cell))
}
fn decode_cell_with_transform(
&self,
cell: &Cell,
_decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
Ok(StrCellData::from(cell))
from_field_type: FieldType,
field: &Field,
) -> Option<<Self as TypeOption>::CellData> {
match from_field_type {
FieldType::RichText
| FieldType::Number
| FieldType::DateTime
| FieldType::SingleSelect
| FieldType::MultiSelect
| FieldType::Checkbox
| FieldType::URL => Some(StrCellData::from(stringify_cell(cell, field))),
FieldType::Checklist
| FieldType::LastEditedTime
| FieldType::CreatedTime
| FieldType::Relation => None,
}
}
fn stringify_cell_data(&self, cell_data: <Self as TypeOption>::CellData) -> String {
cell_data.to_string()
}
fn stringify_cell(&self, cell: &Cell) -> String {
Self::CellData::from(cell).to_string()
}
fn numeric_cell(&self, cell: &Cell) -> Option<f64> {
StrCellData::from(cell).0.parse::<f64>().ok()
}

View File

@ -2,7 +2,7 @@ use std::cmp::Ordering;
use chrono::{DateTime, Local, Offset};
use collab::core::any_map::AnyMapExtension;
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
use serde::{Deserialize, Serialize};
@ -124,17 +124,7 @@ impl TimestampTypeOption {
impl TypeOptionTransform for TimestampTypeOption {}
impl CellDataDecoder for TimestampTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
// Return default data if the type_option_cell_data is not FieldType::CreatedTime nor FieldType::LastEditedTime
if !decoded_field_type.is_last_edited_time() && !decoded_field_type.is_created_time() {
return Ok(Default::default());
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
@ -148,11 +138,6 @@ impl CellDataDecoder for TimestampTypeOption {
}
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.stringify_cell_data(cell_data)
}
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
None
}

View File

@ -2,7 +2,7 @@ use std::cmp::Ordering;
use std::fmt::Debug;
use bytes::Bytes;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::fields::TypeOptionData;
use collab_database::rows::Cell;
use protobuf::ProtobufError;
@ -90,11 +90,6 @@ pub trait TypeOptionCellData {
}
pub trait TypeOptionTransform: TypeOption {
/// Returns true if the current `TypeOption` provides custom type option transformation
fn transformable(&self) -> bool {
false
}
/// Transform the TypeOption from one field type to another
/// For example, when switching from `Checkbox` type option to `Single-Select`
/// type option, adding the `Yes` option if the `Single-select` type-option doesn't contain it.
@ -112,23 +107,6 @@ pub trait TypeOptionTransform: TypeOption {
_old_type_option_data: TypeOptionData,
) {
}
/// Transform the cell data from one field type to another
///
/// # Arguments
///
/// * `cell`: the cell in the current field type
/// * `transformed_field_type`: the cell will be transformed to the is field type's cell data.
/// current `TypeOption` field type.
///
fn transform_type_option_cell(
&self,
_cell: &Cell,
_transformed_field_type: &FieldType,
_field: &Field,
) -> Option<<Self as TypeOption>::CellData> {
None
}
}
pub trait TypeOptionCellDataFilter: TypeOption + CellDataDecoder {

View File

@ -3,18 +3,17 @@ use std::collections::hash_map::DefaultHasher;
use std::hash::{Hash, Hasher};
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, RowId};
use collab_database::rows::{get_field_type_from_cell, Cell, RowId};
use flowy_error::FlowyResult;
use lib_infra::box_any::BoxAny;
use crate::entities::FieldType;
use crate::services::cell::{CellCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob};
use crate::services::field::checklist_type_option::ChecklistTypeOption;
use crate::services::field::{
CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption,
RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, TypeOption,
TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
CheckboxTypeOption, ChecklistTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption,
RelationTypeOption, RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, TypeOption,
TypeOptionCellData, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
TypeOptionTransform, URLTypeOption,
};
use crate::services::sort::SortCondition;
@ -31,10 +30,13 @@ pub const CELL_DATA: &str = "data";
/// 2. there are no generic types parameters.
///
pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
fn handle_cell_protobuf(
/// Format the cell to [BoxCellData] using the passed-in [FieldType] and [Field].
/// The caller can get the cell data by calling [BoxCellData::unbox_or_none].
fn handle_get_boxed_cell_data(&self, cell: &Cell, field: &Field) -> Option<BoxCellData>;
fn handle_get_protobuf_cell_data(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
field_rev: &Field,
) -> FlowyResult<CellProtobufBlob>;
@ -45,190 +47,6 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static {
field: &Field,
) -> FlowyResult<Cell>;
fn handle_cell_compare(
&self,
left_cell: Option<&Cell>,
right_cell: Option<&Cell>,
field: &Field,
sort_condition: SortCondition,
) -> Ordering;
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool;
/// Format the cell to string using the passed-in [FieldType] and [Field].
/// The [Cell] is generic, so we need to know the [FieldType] and [Field] to format the cell.
///
/// For example, the field type of the [TypeOptionCellDataHandler] is [FieldType::Date], and
/// the if field_type is [FieldType::RichText], then the string would be something like "Mar 14, 2022".
///
fn handle_stringify_cell(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String;
fn handle_numeric_cell(&self, cell: &Cell) -> Option<f64>;
/// Format the cell to [BoxCellData] using the passed-in [FieldType] and [Field].
/// The caller can get the cell data by calling [BoxCellData::unbox_or_none].
fn get_cell_data(
&self,
cell: &Cell,
field_type: &FieldType,
field: &Field,
) -> FlowyResult<BoxCellData>;
}
struct CellDataCacheKey(u64);
impl CellDataCacheKey {
pub fn new(field_rev: &Field, decoded_field_type: FieldType, cell: &Cell) -> Self {
let mut hasher = DefaultHasher::new();
if let Some(type_option_data) = field_rev.get_any_type_option(decoded_field_type) {
type_option_data.hash(&mut hasher);
}
hasher.write(field_rev.id.as_bytes());
hasher.write_u8(decoded_field_type as u8);
cell.hash(&mut hasher);
Self(hasher.finish())
}
}
impl AsRef<u64> for CellDataCacheKey {
fn as_ref(&self) -> &u64 {
&self.0
}
}
struct TypeOptionCellDataHandlerImpl<T> {
inner: T,
cell_data_cache: Option<CellCache>,
}
impl<T> TypeOptionCellDataHandlerImpl<T>
where
T: TypeOption
+ CellDataDecoder
+ CellDataChangeset
+ TypeOptionCellDataSerde
+ TypeOptionTransform
+ TypeOptionCellDataFilter
+ TypeOptionCellDataCompare
+ Send
+ Sync
+ 'static,
{
pub fn into_boxed(self) -> Box<dyn TypeOptionCellDataHandler> {
Box::new(self) as Box<dyn TypeOptionCellDataHandler>
}
pub fn new_with_boxed(
inner: T,
cell_data_cache: Option<CellCache>,
) -> Box<dyn TypeOptionCellDataHandler> {
Self {
inner,
cell_data_cache,
}
.into_boxed()
}
}
impl<T> TypeOptionCellDataHandlerImpl<T>
where
T: TypeOption + CellDataDecoder + Send + Sync,
{
fn get_decoded_cell_data(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
field: &Field,
) -> FlowyResult<T::CellData> {
let key = CellDataCacheKey::new(field, *decoded_field_type, cell);
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
let read_guard = cell_data_cache.read();
if let Some(cell_data) = read_guard.get(key.as_ref()).cloned() {
// tracing::trace!(
// "Cell cache hit: field_type:{}, cell: {:?}, cell_data: {:?}",
// decoded_field_type,
// cell,
// cell_data
// );
return Ok(cell_data);
}
}
let cell_data = self.decode_cell(cell, decoded_field_type, field)?;
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
// tracing::trace!(
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
// decoded_field_type,
// cell,
// cell_data
// );
cell_data_cache
.write()
.insert(key.as_ref(), cell_data.clone());
}
Ok(cell_data)
}
fn set_decoded_cell_data(&self, cell: &Cell, cell_data: T::CellData, field: &Field) {
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
let field_type = FieldType::from(field.field_type);
let key = CellDataCacheKey::new(field, field_type, cell);
// tracing::trace!(
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
// field_type,
// cell,
// cell_data
// );
cell_data_cache.write().insert(key.as_ref(), cell_data);
}
}
}
impl<T> std::ops::Deref for TypeOptionCellDataHandlerImpl<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl<T>
where
T: TypeOption
+ CellDataDecoder
+ CellDataChangeset
+ TypeOptionCellDataSerde
+ TypeOptionTransform
+ TypeOptionCellDataFilter
+ TypeOptionCellDataCompare
+ Send
+ Sync
+ 'static,
{
fn handle_cell_protobuf(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
field_rev: &Field,
) -> FlowyResult<CellProtobufBlob> {
let cell_data = self
.get_cell_data(cell, decoded_field_type, field_rev)?
.unbox_or_default::<T::CellData>();
CellProtobufBlob::from(self.protobuf_encode(cell_data))
}
fn handle_cell_changeset(
&self,
cell_changeset: BoxAny,
old_cell: Option<Cell>,
field: &Field,
) -> FlowyResult<Cell> {
let changeset = cell_changeset.unbox_or_error::<T::CellChangeset>()?;
let (cell, cell_data) = self.apply_changeset(changeset, old_cell)?;
self.set_decoded_cell_data(&cell, cell_data, field);
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
@ -256,32 +74,207 @@ where
right_cell: Option<&Cell>,
field: &Field,
sort_condition: SortCondition,
) -> Ordering {
let field_type = FieldType::from(field.field_type);
) -> Ordering;
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool;
/// Format the cell to string using the passed-in [FieldType] and [Field].
/// The [Cell] is generic, so we need to know the [FieldType] and [Field] to format the cell.
///
/// For example, the field type of the [TypeOptionCellDataHandler] is [FieldType::Date], and
/// the if field_type is [FieldType::RichText], then the string would be something like "Mar 14, 2022".
///
fn handle_stringify_cell(&self, cell: &Cell, field: &Field) -> String;
fn handle_numeric_cell(&self, cell: &Cell) -> Option<f64>;
fn handle_is_cell_empty(&self, cell: &Cell, field: &Field) -> bool;
}
struct CellDataCacheKey(u64);
impl CellDataCacheKey {
pub fn new(field_rev: &Field, decoded_field_type: FieldType, cell: &Cell) -> Self {
let mut hasher = DefaultHasher::new();
if let Some(type_option_data) = field_rev.get_any_type_option(decoded_field_type) {
type_option_data.hash(&mut hasher);
}
hasher.write(field_rev.id.as_bytes());
hasher.write_u8(decoded_field_type as u8);
cell.hash(&mut hasher);
Self(hasher.finish())
}
}
impl AsRef<u64> for CellDataCacheKey {
fn as_ref(&self) -> &u64 {
&self.0
}
}
struct TypeOptionCellDataHandlerImpl<T> {
inner: T,
field_type: FieldType,
cell_data_cache: Option<CellCache>,
}
impl<T> TypeOptionCellDataHandlerImpl<T>
where
T: TypeOption
+ CellDataDecoder
+ CellDataChangeset
+ TypeOptionCellDataSerde
+ TypeOptionTransform
+ TypeOptionCellDataFilter
+ TypeOptionCellDataCompare
+ Send
+ Sync
+ 'static,
{
pub fn into_boxed(self) -> Box<dyn TypeOptionCellDataHandler> {
Box::new(self) as Box<dyn TypeOptionCellDataHandler>
}
pub fn new_with_boxed(
inner: T,
field_type: FieldType,
cell_data_cache: Option<CellCache>,
) -> Box<dyn TypeOptionCellDataHandler> {
Self {
inner,
field_type,
cell_data_cache,
}
.into_boxed()
}
}
impl<T> TypeOptionCellDataHandlerImpl<T>
where
T: TypeOption + CellDataDecoder + Send + Sync,
{
fn get_cell_data_cache_key(&self, cell: &Cell, field: &Field) -> CellDataCacheKey {
CellDataCacheKey::new(field, self.field_type, cell)
}
fn get_cell_data_from_cache(&self, cell: &Cell, field: &Field) -> Option<T::CellData> {
let key = self.get_cell_data_cache_key(cell, field);
let cell_data_cache = self.cell_data_cache.as_ref()?.read();
cell_data_cache.get(key.as_ref()).cloned()
}
fn set_cell_data_in_cache(&self, cell: &Cell, cell_data: T::CellData, field: &Field) {
if let Some(cell_data_cache) = self.cell_data_cache.as_ref() {
let field_type = FieldType::from(field.field_type);
let key = CellDataCacheKey::new(field, field_type, cell);
// tracing::trace!(
// "Cell cache update: field_type:{}, cell: {:?}, cell_data: {:?}",
// field_type,
// cell,
// cell_data
// );
cell_data_cache.write().insert(key.as_ref(), cell_data);
}
}
fn get_cell_data(&self, cell: &Cell, field: &Field) -> Option<T::CellData> {
let field_type_of_cell = get_field_type_from_cell(cell)?;
if let Some(cell_data) = self.get_cell_data_from_cache(cell, field) {
return Some(cell_data);
}
let cell_data = if field_type_of_cell == self.field_type {
Some(self.decode_cell(cell).unwrap_or_default())
} else if is_type_option_cell_transformable(field_type_of_cell, self.field_type) {
Some(
self
.decode_cell_with_transform(cell, field_type_of_cell, field)
.unwrap_or_default(),
)
} else {
None
};
if let Some(data) = &cell_data {
self.set_cell_data_in_cache(cell, data.clone(), field);
}
cell_data
}
}
impl<T> std::ops::Deref for TypeOptionCellDataHandlerImpl<T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
impl<T> TypeOptionCellDataHandler for TypeOptionCellDataHandlerImpl<T>
where
T: TypeOption
+ CellDataDecoder
+ CellDataChangeset
+ TypeOptionCellDataSerde
+ TypeOptionTransform
+ TypeOptionCellDataFilter
+ TypeOptionCellDataCompare
+ Send
+ Sync
+ 'static,
{
fn handle_get_boxed_cell_data(&self, cell: &Cell, field: &Field) -> Option<BoxCellData> {
let cell_data = self.get_cell_data(cell, field)?;
Some(BoxCellData::new(cell_data))
}
fn handle_get_protobuf_cell_data(
&self,
cell: &Cell,
field_rev: &Field,
) -> FlowyResult<CellProtobufBlob> {
let cell_data = self.get_cell_data(cell, field_rev).unwrap_or_default();
CellProtobufBlob::from(self.protobuf_encode(cell_data))
}
fn handle_cell_changeset(
&self,
cell_changeset: BoxAny,
old_cell: Option<Cell>,
field: &Field,
) -> FlowyResult<Cell> {
let changeset = cell_changeset.unbox_or_error::<T::CellChangeset>()?;
let (cell, cell_data) = self.apply_changeset(changeset, old_cell)?;
self.set_cell_data_in_cache(&cell, cell_data, field);
Ok(cell)
}
fn handle_cell_compare(
&self,
left_cell: Option<&Cell>,
right_cell: Option<&Cell>,
field: &Field,
sort_condition: SortCondition,
) -> Ordering {
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();
let right_cell_data = self.get_cell_data(right_cell, 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();
let left_cell_data = self.get_cell_data(left_cell, 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();
let left_cell_data = self.get_cell_data(left_cell, field).unwrap_or_default();
let right_cell_data = self.get_cell_data(right_cell, field).unwrap_or_default();
self.apply_cmp(&left_cell_data, &right_cell_data, sort_condition)
},
@ -290,9 +283,8 @@ where
fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool {
let perform_filter = || {
let field_type = FieldType::from(field.field_type);
let cell_filter = filter.downcast_ref::<T::CellFilter>()?;
let cell_data = self.get_decoded_cell_data(cell, &field_type, field).ok()?;
let cell_data = self.get_cell_data(cell, field).unwrap_or_default();
Some(self.apply_filter(cell_filter, &cell_data))
};
@ -306,36 +298,24 @@ where
/// is [FieldType::RichText], then the string will be transformed to a string that separated by comma with the
/// option's name.
///
fn handle_stringify_cell(&self, cell: &Cell, field_type: &FieldType, field: &Field) -> String {
if self.transformable() {
let cell_data = self.transform_type_option_cell(cell, field_type, field);
fn handle_stringify_cell(&self, cell: &Cell, field: &Field) -> String {
if is_type_option_cell_transformable(self.field_type, FieldType::RichText) {
let cell_data = self.get_cell_data(cell, field);
if let Some(cell_data) = cell_data {
return self.stringify_cell_data(cell_data);
}
}
self.stringify_cell(cell)
"".to_string()
}
fn handle_numeric_cell(&self, cell: &Cell) -> Option<f64> {
self.numeric_cell(cell)
}
fn get_cell_data(
&self,
cell: &Cell,
field_type: &FieldType,
field: &Field,
) -> FlowyResult<BoxCellData> {
// tracing::debug!("get_cell_data: {:?}", std::any::type_name::<Self>());
let cell_data = if self.transformable() {
match self.transform_type_option_cell(cell, field_type, field) {
None => self.get_decoded_cell_data(cell, field_type, field)?,
Some(cell_data) => cell_data,
}
} else {
self.get_decoded_cell_data(cell, field_type, field)?
};
Ok(BoxCellData::new(cell_data))
fn handle_is_cell_empty(&self, cell: &Cell, field: &Field) -> bool {
let cell_data = self.get_cell_data(cell, field).unwrap_or_default();
cell_data.is_cell_empty()
}
}
@ -352,98 +332,150 @@ impl<'a> TypeOptionCellExt<'a> {
}
}
pub fn get_cells<T>(&self) -> Vec<T> {
let field_type = FieldType::from(self.field.field_type);
match self.get_type_option_cell_data_handler(&field_type) {
None => vec![],
Some(_handler) => {
todo!()
},
}
}
pub fn get_type_option_cell_data_handler(
pub fn get_type_option_cell_data_handler_with_field_type(
&self,
field_type: &FieldType,
field_type: FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
match field_type {
FieldType::RichText => self
.field
.get_type_option::<RichTextTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::Number => self
.field
.get_type_option::<NumberTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::DateTime => self
.field
.get_type_option::<DateTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::LastEditedTime | FieldType::CreatedTime => self
.field
.get_type_option::<TimestampTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::SingleSelect => self
.field
.get_type_option::<SingleSelectTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::MultiSelect => self
.field
.get_type_option::<MultiSelectTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::Checkbox => self
.field
.get_type_option::<CheckboxTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::URL => {
self
.field
.get_type_option::<URLTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
})
},
FieldType::Checklist => self
.field
.get_type_option::<ChecklistTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
FieldType::Relation => self
.field
.get_type_option::<RelationTypeOption>(field_type)
.map(|type_option| {
TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone())
TypeOptionCellDataHandlerImpl::new_with_boxed(
type_option,
field_type,
self.cell_data_cache.clone(),
)
}),
}
}
pub fn get_type_option_cell_data_handler(&self) -> Option<Box<dyn TypeOptionCellDataHandler>> {
let field_type = FieldType::from(self.field.field_type);
self.get_type_option_cell_data_handler_with_field_type(field_type)
}
}
pub fn is_type_option_cell_transformable(
from_field_type: FieldType,
to_field_type: FieldType,
) -> bool {
matches!(
(from_field_type, to_field_type),
(FieldType::Checkbox, FieldType::SingleSelect)
| (FieldType::Checkbox, FieldType::MultiSelect)
| (FieldType::SingleSelect, FieldType::MultiSelect)
| (FieldType::MultiSelect, FieldType::SingleSelect)
| (_, FieldType::RichText)
)
}
pub fn transform_type_option(
type_option_data: &TypeOptionData,
old_field_type: FieldType,
new_field_type: FieldType,
old_type_option_data: Option<TypeOptionData>,
old_field_type: FieldType,
new_type_option_data: TypeOptionData,
) -> TypeOptionData {
let mut transform_handler = get_type_option_transform_handler(type_option_data, new_field_type);
if let Some(old_type_option_data) = old_type_option_data {
let mut transform_handler =
get_type_option_transform_handler(new_type_option_data, new_field_type);
transform_handler.transform(old_field_type, old_type_option_data);
transform_handler.to_type_option_data()
} else {
new_type_option_data
}
transform_handler.to_type_option_data()
}
/// A helper trait that used to erase the `Self` of `TypeOption` trait to make it become a Object-safe trait.
@ -459,27 +491,25 @@ pub trait TypeOptionTransformHandler {
impl<T> TypeOptionTransformHandler for T
where
T: TypeOptionTransform + Into<TypeOptionData> + Clone,
T: TypeOptionTransform + Clone,
{
fn transform(
&mut self,
old_type_option_field_type: FieldType,
old_type_option_data: TypeOptionData,
) {
if self.transformable() {
self.transform_type_option(old_type_option_field_type, old_type_option_data)
}
self.transform_type_option(old_type_option_field_type, old_type_option_data)
}
fn to_type_option_data(&self) -> TypeOptionData {
self.clone().into()
}
}
fn get_type_option_transform_handler(
type_option_data: &TypeOptionData,
type_option_data: TypeOptionData,
field_type: FieldType,
) -> Box<dyn TypeOptionTransformHandler> {
let type_option_data = type_option_data.clone();
match field_type {
FieldType::RichText => {
Box::new(RichTextTypeOption::from(type_option_data)) as Box<dyn TypeOptionTransformHandler>

View File

@ -1,4 +1,4 @@
use crate::entities::{FieldType, TextFilterPB, URLCellDataPB};
use crate::entities::{TextFilterPB, URLCellDataPB};
use crate::services::cell::{CellDataChangeset, CellDataDecoder};
use crate::services::field::{
TypeOption, TypeOptionCellDataCompare, TypeOptionCellDataFilter, TypeOptionCellDataSerde,
@ -7,7 +7,7 @@ use crate::services::field::{
use crate::services::sort::SortCondition;
use collab::core::any_map::AnyMapExtension;
use collab_database::fields::{Field, TypeOptionData, TypeOptionDataBuilder};
use collab_database::fields::{TypeOptionData, TypeOptionDataBuilder};
use collab_database::rows::Cell;
use fancy_regex::Regex;
use flowy_error::FlowyResult;
@ -61,16 +61,7 @@ impl TypeOptionCellDataSerde for URLTypeOption {
}
impl CellDataDecoder for URLTypeOption {
fn decode_cell(
&self,
cell: &Cell,
decoded_field_type: &FieldType,
_field: &Field,
) -> FlowyResult<<Self as TypeOption>::CellData> {
if !decoded_field_type.is_url() {
return Ok(Default::default());
}
fn decode_cell(&self, cell: &Cell) -> FlowyResult<<Self as TypeOption>::CellData> {
self.parse_cell(cell)
}
@ -78,11 +69,6 @@ impl CellDataDecoder for URLTypeOption {
cell_data.data
}
fn stringify_cell(&self, cell: &Cell) -> String {
let cell_data = Self::CellData::from(cell);
self.stringify_cell_data(cell_data)
}
fn numeric_cell(&self, _cell: &Cell) -> Option<f64> {
None
}

View File

@ -528,9 +528,8 @@ fn apply_filter(
return Some(false);
}
let cell = row.cells.get(field_id).cloned();
let field_type = FieldType::from(field.field_type);
if let Some(handler) = TypeOptionCellExt::new(field, Some(cell_data_cache.clone()))
.get_type_option_cell_data_handler(&field_type)
.get_type_option_cell_data_handler()
{
Some(handler.handle_cell_filter(field, &cell.unwrap_or_default(), condition_and_content))
} else {

View File

@ -3,8 +3,7 @@ use indexmap::IndexMap;
use flowy_error::{FlowyError, FlowyResult};
use crate::entities::FieldType;
use crate::services::cell::stringify_cell_data;
use crate::services::cell::stringify_cell;
#[derive(Debug, Clone, Copy)]
pub enum CSVFormat {
@ -46,12 +45,9 @@ impl CSVExport {
.iter()
.map(|(field_id, field)| match row.cells.get(field_id) {
None => "".to_string(),
Some(cell) => {
let field_type = FieldType::from(field.field_type);
match style {
CSVFormat::Original => stringify_cell_data(cell, &field_type, &field_type, field),
CSVFormat::META => serde_json::to_string(cell).unwrap_or_else(|_| "".to_string()),
}
Some(cell) => match style {
CSVFormat::Original => stringify_cell(cell, field),
CSVFormat::META => serde_json::to_string(cell).unwrap_or_else(|_| "".to_string()),
},
})
.collect::<Vec<_>>();

View File

@ -336,7 +336,6 @@ fn cmp_row(
.as_ref()
.map_or_else(|| right.cells.get(&sort.field_id), |cell| cell.1.as_ref()),
field_rev,
field_type,
cell_data_cache,
sort.condition,
)
@ -348,12 +347,11 @@ fn cmp_cell(
left_cell: Option<&Cell>,
right_cell: Option<&Cell>,
field: &Field,
field_type: FieldType,
cell_data_cache: &CellCache,
sort_condition: SortCondition,
) -> Ordering {
match TypeOptionCellExt::new(field, Some(cell_data_cache.clone()))
.get_type_option_cell_data_handler(&field_type)
.get_type_option_cell_data_handler()
{
None => default_order(),
Some(handler) => handler.handle_cell_compare(left_cell, right_cell, field, sort_condition),

View File

@ -1,7 +1,7 @@
use collab_database::fields::{Field, TypeOptionData};
use flowy_database2::entities::{CreateFieldParams, FieldChangesetParams, FieldType};
use flowy_database2::services::cell::stringify_cell_data;
use flowy_database2::services::cell::stringify_cell;
use crate::database::database_editor::DatabaseEditorTest;
@ -31,7 +31,6 @@ pub enum FieldScript {
AssertCellContent {
field_id: String,
row_index: usize,
from_field_type: FieldType,
expected_content: String,
},
}
@ -118,17 +117,15 @@ impl DatabaseFieldTest {
FieldScript::AssertCellContent {
field_id,
row_index,
from_field_type,
expected_content,
} => {
let field = self.editor.get_field(&field_id).unwrap();
let field_type = FieldType::from(field.field_type);
let rows = self.editor.get_rows(&self.view_id()).await.unwrap();
let row_detail = rows.get(row_index).unwrap();
let cell = row_detail.row.cells.get(&field_id).unwrap().clone();
let content = stringify_cell_data(&cell, &from_field_type, &field_type, &field);
let content = stringify_cell(&cell, &field);
assert_eq!(content, expected_content);
},
}

View File

@ -155,8 +155,6 @@ async fn grid_switch_from_checkbox_to_select_option_test() {
field_id: checkbox_field.id.clone(),
// the mock data of the checkbox with row_index one is "true"
row_index: 1,
// the from_field_type represents as the current field type
from_field_type: FieldType::Checkbox,
// The content of the checkbox should transform to the corresponding option name.
expected_content: CHECK.to_string(),
},
@ -190,7 +188,6 @@ async fn grid_switch_from_multi_select_to_text_test() {
let script_assert_field = vec![AssertCellContent {
field_id: field_rev.id.clone(),
row_index: 0,
from_field_type: FieldType::MultiSelect,
expected_content: format!(
"{},{}",
multi_select_type_option.first().unwrap().name,
@ -218,13 +215,11 @@ async fn grid_switch_from_checkbox_to_text_test() {
AssertCellContent {
field_id: field_rev.id.clone(),
row_index: 1,
from_field_type: FieldType::Checkbox,
expected_content: "Yes".to_string(),
},
AssertCellContent {
field_id: field_rev.id.clone(),
row_index: 2,
from_field_type: FieldType::Checkbox,
expected_content: "No".to_string(),
},
];
@ -246,13 +241,11 @@ async fn grid_switch_from_date_to_text_test() {
AssertCellContent {
field_id: field.id.clone(),
row_index: 2,
from_field_type: FieldType::DateTime,
expected_content: "2022/03/14".to_string(),
},
AssertCellContent {
field_id: field.id.clone(),
row_index: 3,
from_field_type: FieldType::DateTime,
expected_content: "2022/11/17".to_string(),
},
];
@ -275,13 +268,11 @@ async fn grid_switch_from_number_to_text_test() {
AssertCellContent {
field_id: field.id.clone(),
row_index: 0,
from_field_type: FieldType::Number,
expected_content: "$1".to_string(),
},
AssertCellContent {
field_id: field.id.clone(),
row_index: 4,
from_field_type: FieldType::Number,
expected_content: "".to_string(),
},
];
@ -303,7 +294,6 @@ async fn grid_switch_from_checklist_to_text_test() {
AssertCellContent {
field_id: field_rev.id.clone(),
row_index: 0,
from_field_type: FieldType::Checklist,
expected_content: "First thing".to_string(),
},
];

View File

@ -48,7 +48,7 @@ async fn according_to_text_contains_filter_test() {
AssertCellContent {
field_id: text_field.id,
row_index: test.row_details.len() - 1,
from_field_type: FieldType::RichText,
expected_content: "sample".to_string(),
},
];
@ -195,7 +195,7 @@ async fn according_to_checkbox_is_checked_filter_test() {
AssertCellContent {
field_id: checkbox_field.id,
row_index: 3,
from_field_type: FieldType::Checkbox,
expected_content: "Yes".to_string(),
},
];
@ -242,7 +242,7 @@ async fn according_to_date_time_is_filter_test() {
AssertCellContent {
field_id: datetime_field.id,
row_index: 0,
from_field_type: FieldType::DateTime,
expected_content: "2024/03/15".to_string(),
},
];
@ -331,7 +331,7 @@ async fn according_to_select_option_is_filter_test() {
AssertCellContent {
field_id: multi_select_field.id,
row_index: 1,
from_field_type: FieldType::MultiSelect,
expected_content: stringified_expected,
},
];
@ -380,7 +380,7 @@ async fn according_to_select_option_contains_filter_test() {
AssertCellContent {
field_id: multi_select_field.id,
row_index: 5,
from_field_type: FieldType::MultiSelect,
expected_content: stringified_expected,
},
];
@ -424,7 +424,7 @@ async fn according_to_select_option_is_not_empty_filter_test() {
AssertCellContent {
field_id: multi_select_field.id,
row_index: 5,
from_field_type: FieldType::MultiSelect,
expected_content: stringified_expected,
},
];

View File

@ -35,7 +35,7 @@ async fn row_data_payload_with_empty_hashmap_test() {
AssertCellContent {
field_id: text_field.id,
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: "".to_string(),
},
];
@ -70,7 +70,7 @@ async fn row_data_payload_with_unknown_field_id_test() {
AssertCellContent {
field_id: text_field.id.clone(),
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: "".to_string(),
},
AssertCellExistence {
@ -107,7 +107,7 @@ async fn row_data_payload_with_empty_string_text_data_test() {
AssertCellContent {
field_id: text_field.id,
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: cell_data.to_string(),
},
];
@ -139,7 +139,7 @@ async fn row_data_payload_with_text_data_test() {
AssertCellContent {
field_id: text_field.id.clone(),
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: cell_data.to_string(),
},
];
@ -180,7 +180,7 @@ async fn row_data_payload_with_multi_text_data_test() {
AssertCellContent {
field_id: text_field.id,
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: text_cell_data.to_string(),
},
AssertCellExistence {
@ -191,7 +191,7 @@ async fn row_data_payload_with_multi_text_data_test() {
AssertCellContent {
field_id: number_field.id,
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: "$1,234".to_string(),
},
AssertCellExistence {
@ -202,7 +202,7 @@ async fn row_data_payload_with_multi_text_data_test() {
AssertCellContent {
field_id: url_field.id,
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: url_cell_data.to_string(),
},
];
@ -234,7 +234,7 @@ async fn row_data_payload_with_date_time_test() {
AssertCellContent {
field_id: date_field.id.clone(),
row_index: test.row_details.len(),
from_field_type: FieldType::RichText,
expected_content: "2024/03/15".to_string(),
},
];
@ -296,7 +296,7 @@ async fn row_data_payload_with_checkbox_test() {
AssertCellContent {
field_id: checkbox_field.id.clone(),
row_index: test.row_details.len(),
from_field_type: FieldType::Checkbox,
expected_content: cell_data.to_string(),
},
];
@ -340,7 +340,7 @@ async fn row_data_payload_with_select_option_test() {
AssertCellContent {
field_id: multi_select_field.id.clone(),
row_index: test.row_details.len(),
from_field_type: FieldType::MultiSelect,
expected_content: stringified_cell_data,
},
];

View File

@ -1,8 +1,8 @@
use std::ops::{Deref, DerefMut};
use std::time::Duration;
use flowy_database2::entities::{CreateRowPayloadPB, FieldType, FilterDataPB, InsertFilterPB};
use flowy_database2::services::cell::stringify_cell_data;
use flowy_database2::entities::{CreateRowPayloadPB, FilterDataPB, InsertFilterPB};
use flowy_database2::services::cell::stringify_cell;
use flowy_database2::services::field::{SelectOptionIds, SELECTION_IDS_SEPARATOR};
use crate::database::database_editor::DatabaseEditorTest;
@ -24,7 +24,6 @@ pub enum PreFillRowCellTestScript {
AssertCellContent {
field_id: String,
row_index: usize,
from_field_type: FieldType,
expected_content: String,
},
AssertSelectOptionCellStrict {
@ -105,11 +104,9 @@ impl DatabasePreFillRowCellTest {
PreFillRowCellTestScript::AssertCellContent {
field_id,
row_index,
from_field_type,
expected_content,
} => {
let field = self.editor.get_field(&field_id).unwrap();
let field_type = FieldType::from(field.field_type);
let rows = self.editor.get_rows(&self.view_id).await.unwrap();
let row_detail = rows.get(row_index).unwrap();
@ -120,7 +117,7 @@ impl DatabasePreFillRowCellTest {
.get(&field_id)
.cloned()
.unwrap_or_default();
let content = stringify_cell_data(&cell, &from_field_type, &field_type, &field);
let content = stringify_cell(&cell, &field);
assert_eq!(content, expected_content);
},
PreFillRowCellTestScript::AssertSelectOptionCellStrict {

View File

@ -1,5 +1,5 @@
use flowy_database2::entities::FieldType;
use flowy_database2::services::cell::stringify_cell_data;
use flowy_database2::services::cell::stringify_cell;
use flowy_database2::services::field::CHECK;
use flowy_database2::services::share::csv::CSVFormat;
@ -67,7 +67,7 @@ async fn export_and_then_import_meta_csv_test() {
for (index, row_detail) in rows.iter().enumerate() {
if let Some(cell) = row_detail.row.cells.get(&field.id) {
let field_type = FieldType::from(field.field_type);
let s = stringify_cell_data(cell, &field_type, &field_type, &field);
let s = stringify_cell(cell, &field);
match &field_type {
FieldType::RichText => {
if index == 0 {
@ -141,7 +141,7 @@ async fn history_database_import_test() {
for (index, row_detail) in rows.iter().enumerate() {
if let Some(cell) = row_detail.row.cells.get(&field.id) {
let field_type = FieldType::from(field.field_type);
let s = stringify_cell_data(cell, &field_type, &field_type, &field);
let s = stringify_cell(cell, &field);
match &field_type {
FieldType::RichText => {
if index == 0 {

View File

@ -8,9 +8,9 @@ use futures::stream::StreamExt;
use tokio::sync::broadcast::Receiver;
use flowy_database2::entities::{
CreateRowPayloadPB, DeleteSortPayloadPB, FieldType, ReorderSortPayloadPB, UpdateSortPayloadPB,
CreateRowPayloadPB, DeleteSortPayloadPB, ReorderSortPayloadPB, UpdateSortPayloadPB,
};
use flowy_database2::services::cell::stringify_cell_data;
use flowy_database2::services::cell::stringify_cell;
use flowy_database2::services::database_view::DatabaseViewChanged;
use flowy_database2::services::sort::SortCondition;
@ -119,10 +119,9 @@ impl DatabaseSortTest {
let mut cells = vec![];
let rows = self.editor.get_rows(&self.view_id).await.unwrap();
let field = self.editor.get_field(&field_id).unwrap();
let field_type = FieldType::from(field.field_type);
for row_detail in rows {
if let Some(cell) = row_detail.row.cells.get(&field_id) {
let content = stringify_cell_data(cell, &field_type, &field_type, &field);
let content = stringify_cell(cell, &field);
cells.push(content);
} else {
cells.push("".to_string());