refactor: database field type option (#4136)

* refactor: include field type option in FieldPB

* refactor: adapt changes on flutter

* refactor: adapt changes on new tauri grid

* refactor: adapt changes on old tauri grid/board

* chore: merge
This commit is contained in:
Richard Shiue
2023-12-20 11:11:38 +08:00
committed by GitHub
parent 9a1ea138fc
commit d68c847d59
73 changed files with 1326 additions and 2459 deletions

View File

@ -14,6 +14,7 @@ use flowy_error::ErrorCode;
use crate::entities::parser::NotEmptyStr;
use crate::entities::position_entities::OrderObjectPositionPB;
use crate::impl_into_field_type;
use crate::services::field::{default_type_option_data_from_type, type_option_to_pb};
/// [FieldPB] defines a Field's attributes. Such as the name, field_type, and width. etc.
#[derive(Debug, Clone, Default, ProtoBuf)]
@ -35,17 +36,25 @@ pub struct FieldPB {
#[pb(index = 6)]
pub is_primary: bool,
#[pb(index = 7)]
pub type_option_data: Vec<u8>,
}
impl std::convert::From<Field> for FieldPB {
fn from(field: Field) -> Self {
impl FieldPB {
pub fn new(field: Field) -> Self {
let field_type = field.field_type.into();
let type_option = field
.get_any_type_option(field_type)
.unwrap_or_else(|| default_type_option_data_from_type(&field_type));
Self {
id: field.id,
name: field.name,
field_type: FieldType::from(field.field_type),
field_type,
visibility: field.visibility,
width: field.width as i32,
is_primary: field.is_primary,
type_option_data: type_option_to_pb(type_option, &field_type).to_vec(),
}
}
}
@ -139,15 +148,6 @@ pub struct IndexFieldPB {
pub index: i32,
}
impl IndexFieldPB {
pub fn from_field(field: Field, index: usize) -> Self {
Self {
field: FieldPB::from(field),
index: index as i32,
}
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct CreateFieldPayloadPB {
#[pb(index = 1)]
@ -236,50 +236,6 @@ impl TryInto<EditFieldParams> for UpdateFieldTypePayloadPB {
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct TypeOptionPathPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub field_id: String,
#[pb(index = 3)]
pub field_type: FieldType,
}
pub struct TypeOptionPathParams {
pub view_id: String,
pub field_id: String,
pub field_type: FieldType,
}
impl TryInto<TypeOptionPathParams> for TypeOptionPathPB {
type Error = ErrorCode;
fn try_into(self) -> Result<TypeOptionPathParams, Self::Error> {
let database_id = NotEmptyStr::parse(self.view_id).map_err(|_| ErrorCode::DatabaseIdIsEmpty)?;
let field_id = NotEmptyStr::parse(self.field_id).map_err(|_| ErrorCode::FieldIdIsEmpty)?;
Ok(TypeOptionPathParams {
view_id: database_id.0,
field_id: field_id.0,
field_type: self.field_type,
})
}
}
#[derive(Debug, Default, ProtoBuf)]
pub struct TypeOptionPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub field: FieldPB,
#[pb(index = 3)]
pub type_option_data: Vec<u8>,
}
/// Collection of the [FieldPB]
#[derive(Debug, Default, ProtoBuf)]
pub struct RepeatedFieldPB {

View File

@ -13,7 +13,7 @@ use crate::manager::DatabaseManager;
use crate::services::cell::CellBuilder;
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::{
type_option_data_from_pb_or_default, DateCellChangeset, SelectOptionCellChangeset,
type_option_data_from_pb, DateCellChangeset, SelectOptionCellChangeset,
};
use crate::services::field_settings::FieldSettingsChangesetParams;
use crate::services::group::GroupChangeset;
@ -160,7 +160,7 @@ pub(crate) async fn get_fields_handler(
let fields = database_editor
.get_fields(&params.view_id, params.field_ids)
.into_iter()
.map(FieldPB::from)
.map(FieldPB::new)
.collect::<Vec<FieldPB>>()
.into();
data_result_ok(fields)
@ -178,7 +178,7 @@ pub(crate) async fn get_primary_field_handler(
.get_fields(&view_id, None)
.into_iter()
.filter(|field| field.is_primary)
.map(FieldPB::from)
.map(FieldPB::new)
.collect::<Vec<FieldPB>>();
if fields.is_empty() {
@ -217,8 +217,7 @@ pub(crate) async fn update_field_type_option_handler(
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
if let Some(old_field) = database_editor.get_field(&params.field_id) {
let field_type = FieldType::from(old_field.field_type);
let type_option_data =
type_option_data_from_pb_or_default(params.type_option_data, &field_type);
let type_option_data = type_option_data_from_pb(params.type_option_data, &field_type)?;
database_editor
.update_field_type_option(
&params.view_id,
@ -293,46 +292,19 @@ pub(crate) async fn duplicate_field_handler(
Ok(())
}
/// Return the FieldTypeOptionData if the Field exists otherwise return record not found error.
#[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn get_field_type_option_data_handler(
data: AFPluginData<TypeOptionPathPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<TypeOptionPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let params: TypeOptionPathParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
if let Some((field, data)) = database_editor
.get_field_type_option_data(&params.field_id)
.await
{
let data = TypeOptionPB {
view_id: params.view_id,
field: FieldPB::from(field),
type_option_data: data.to_vec(),
};
data_result_ok(data)
} else {
Err(FlowyError::record_not_found())
}
}
/// Create TypeOptionPB and save it. Return the FieldTypeOptionData.
/// Create a field and save it. Returns the [FieldPB] in the current view.
#[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn create_field_handler(
data: AFPluginData<CreateFieldPayloadPB>,
manager: AFPluginState<Weak<DatabaseManager>>,
) -> DataResult<TypeOptionPB, FlowyError> {
) -> DataResult<FieldPB, FlowyError> {
let manager = upgrade_manager(manager)?;
let params: CreateFieldParams = data.into_inner().try_into()?;
let database_editor = manager.get_database_with_view_id(&params.view_id).await?;
let (field, data) = database_editor.create_field_with_type_option(&params).await;
let data = database_editor
.create_field_with_type_option(params)
.await?;
let data = TypeOptionPB {
view_id: params.view_id,
field: FieldPB::from(field),
type_option_data: data.to_vec(),
};
data_result_ok(data)
}

View File

@ -30,7 +30,6 @@ pub fn init(database_manager: Weak<DatabaseManager>) -> AFPlugin {
.event(DatabaseEvent::UpdateFieldType, switch_to_field_handler)
.event(DatabaseEvent::DuplicateField, duplicate_field_handler)
.event(DatabaseEvent::MoveField, move_field_handler)
.event(DatabaseEvent::GetTypeOption, get_field_type_option_data_handler)
.event(DatabaseEvent::CreateField, create_field_handler)
// Row
.event(DatabaseEvent::CreateRow, create_row_handler)
@ -174,18 +173,9 @@ pub enum DatabaseEvent {
#[event(input = "MoveFieldPayloadPB")]
MoveField = 22,
/// [TypeOptionPathPB] event is used to get the FieldTypeOption data for a specific field type.
///
/// Check out the [TypeOptionPB] for more details. If the [FieldTypeOptionData] does exist
/// for the target type, the [TypeOptionBuilder] will create the default data for that type.
///
/// Return the [TypeOptionPB] if there are no errors.
#[event(input = "TypeOptionPathPB", output = "TypeOptionPB")]
GetTypeOption = 23,
/// [CreateField] event is used to create a new field with an optional
/// TypeOptionData.
#[event(input = "CreateFieldPayloadPB", output = "TypeOptionPB")]
#[event(input = "CreateFieldPayloadPB", output = "FieldPB")]
CreateField = 24,
#[event(input = "DatabaseViewIdPB", output = "FieldPB")]

View File

@ -1,7 +1,6 @@
use std::collections::HashMap;
use std::sync::Arc;
use bytes::Bytes;
use collab_database::database::MutexDatabase;
use collab_database::fields::{Field, TypeOptionData};
use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId};
@ -28,8 +27,8 @@ use crate::services::database_view::{
use crate::services::field::checklist_type_option::ChecklistCellChangeset;
use crate::services::field::{
default_type_option_data_from_type, select_type_option_from_field, transform_type_option,
type_option_data_from_pb_or_default, type_option_to_pb, SelectOptionCellChangeset,
SelectOptionIds, TimestampCellData, TypeOptionCellDataHandler, TypeOptionCellExt,
type_option_data_from_pb, SelectOptionCellChangeset, SelectOptionIds, TimestampCellData,
TypeOptionCellDataHandler, TypeOptionCellExt,
};
use crate::services::field_settings::{
default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams,
@ -469,28 +468,20 @@ impl DatabaseEditor {
Ok(None)
}
pub async fn get_field_type_option_data(&self, field_id: &str) -> Option<(Field, Bytes)> {
let field = self.database.lock().fields.get_field(field_id);
field.map(|field| {
let field_type = FieldType::from(field.field_type);
let type_option = field
.get_any_type_option(field_type)
.unwrap_or_else(|| default_type_option_data_from_type(&field_type));
(field, type_option_to_pb(type_option, &field_type))
})
}
pub async fn create_field_with_type_option(&self, params: &CreateFieldParams) -> (Field, Bytes) {
pub async fn create_field_with_type_option(
&self,
params: CreateFieldParams,
) -> FlowyResult<FieldPB> {
let name = params
.field_name
.clone()
.unwrap_or_else(|| params.field_type.default_name());
let type_option_data = match &params.type_option_data {
None => default_type_option_data_from_type(&params.field_type),
Some(type_option_data) => {
type_option_data_from_pb_or_default(type_option_data.clone(), &params.field_type)
},
};
let type_option_data = params
.type_option_data
.and_then(|data| type_option_data_from_pb(data, &params.field_type).ok())
.unwrap_or(default_type_option_data_from_type(&params.field_type));
let (index, field) = self.database.lock().create_field_with_mut(
&params.view_id,
name,
@ -499,7 +490,7 @@ impl DatabaseEditor {
|field| {
field
.type_options
.insert(params.field_type.to_string(), type_option_data.clone());
.insert(params.field_type.to_string(), type_option_data);
},
default_field_settings_by_layout_map(),
);
@ -508,10 +499,7 @@ impl DatabaseEditor {
.notify_did_insert_database_field(field.clone(), index)
.await;
(
field,
type_option_to_pb(type_option_data, &params.field_type),
)
Ok(FieldPB::new(field))
}
pub async fn move_field(&self, params: MoveFieldParams) -> FlowyResult<()> {
@ -539,7 +527,10 @@ impl DatabaseEditor {
if let Some(index) = new_index {
let delete_field = FieldIdPB::from(params.from_field_id);
let insert_field = IndexFieldPB::from_field(field, index);
let insert_field = IndexFieldPB {
field: FieldPB::new(field),
index: index as i32,
};
let notified_changeset = DatabaseFieldChangesetPB {
view_id: params.view_id,
inserted_fields: vec![insert_field],
@ -1058,7 +1049,10 @@ impl DatabaseEditor {
#[tracing::instrument(level = "trace", skip_all, err)]
async fn notify_did_insert_database_field(&self, field: Field, index: usize) -> FlowyResult<()> {
let database_id = self.database.lock().get_database_id();
let index_field = IndexFieldPB::from_field(field, index);
let index_field = IndexFieldPB {
field: FieldPB::new(field),
index: index as i32,
};
let notified_changeset = DatabaseFieldChangesetPB::insert(&database_id, vec![index_field]);
let _ = self.notify_did_update_database(notified_changeset).await;
Ok(())
@ -1629,7 +1623,7 @@ fn notify_did_update_database_field(
};
if let Some(field) = field {
let updated_field = FieldPB::from(field);
let updated_field = FieldPB::new(field);
let notified_changeset =
DatabaseFieldChangesetPB::update(&database_id, vec![updated_field.clone()]);

View File

@ -175,12 +175,12 @@ pub trait TypeOptionCellDataCompare: TypeOption {
}
}
pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
pub fn type_option_data_from_pb<T: Into<Bytes>>(
bytes: T,
field_type: &FieldType,
) -> TypeOptionData {
) -> Result<TypeOptionData, ProtobufError> {
let bytes = bytes.into();
let result: Result<TypeOptionData, ProtobufError> = match field_type {
match field_type {
FieldType::RichText => {
RichTextTypeOptionPB::try_from(bytes).map(|pb| RichTextTypeOption::from(pb).into())
},
@ -206,9 +206,7 @@ pub fn type_option_data_from_pb_or_default<T: Into<Bytes>>(
FieldType::Checklist => {
ChecklistTypeOptionPB::try_from(bytes).map(|pb| ChecklistTypeOption::from(pb).into())
},
};
result.unwrap_or_else(|_| default_type_option_data_from_type(field_type))
}
}
pub fn type_option_to_pb(type_option: TypeOptionData, field_type: &FieldType) -> Bytes {

View File

@ -64,7 +64,7 @@ impl DatabaseFieldTest {
match script {
FieldScript::CreateField { params } => {
self.field_count += 1;
self.editor.create_field_with_type_option(&params).await;
let _ = self.editor.create_field_with_type_option(params).await;
let fields = self.editor.get_fields(&self.view_id, None);
assert_eq!(self.field_count, fields.len());
},