mirror of
https://github.com/AppFlowy-IO/AppFlowy.git
synced 2024-08-30 18:12:39 +00:00
refactor: row builder
This commit is contained in:
parent
6020459f76
commit
284755eccf
@ -5,6 +5,7 @@ use crate::services::field::select_option::*;
|
||||
use crate::services::field::{
|
||||
default_type_option_builder_from_type, type_option_builder_from_json_str, DateChangesetParams, DateChangesetPayload,
|
||||
};
|
||||
use crate::services::row::make_row_from_row_rev;
|
||||
use flowy_error::{ErrorCode, FlowyError, FlowyResult};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use flowy_sync::entities::grid::{FieldChangesetParams, GridSettingChangesetParams};
|
||||
@ -229,10 +230,12 @@ pub(crate) async fn get_row_handler(
|
||||
) -> DataResult<OptionalRow, FlowyError> {
|
||||
let params: GridRowId = data.into_inner().try_into()?;
|
||||
let editor = manager.get_grid_editor(¶ms.grid_id)?;
|
||||
let row = OptionalRow {
|
||||
row: editor.get_row(¶ms.row_id).await?,
|
||||
};
|
||||
data_result(row)
|
||||
let row = editor
|
||||
.get_row_rev(¶ms.row_id)
|
||||
.await?
|
||||
.and_then(make_row_from_row_rev);
|
||||
|
||||
data_result(OptionalRow { row })
|
||||
}
|
||||
|
||||
#[tracing::instrument(level = "debug", skip(data, manager), err)]
|
||||
|
@ -26,8 +26,12 @@ pub trait CellDataOperation<D, C> {
|
||||
/// SelectOptionCellChangeset,DateCellChangeset. etc.
|
||||
fn apply_changeset(&self, changeset: CellDataChangeset<C>, cell_rev: Option<CellRevision>) -> FlowyResult<String>;
|
||||
}
|
||||
/// The changeset will be deserialized into specific data base on the FieldType.
|
||||
/// For example, it's String on FieldType::RichText, and SelectOptionChangeset on FieldType::SingleSelect
|
||||
/// changeset: It will be deserialized into specific data base on the FieldType.
|
||||
/// For example,
|
||||
/// FieldType::RichText => String
|
||||
/// FieldType::SingleSelect => SelectOptionChangeset
|
||||
///
|
||||
/// cell_rev: It will be None if the cell does not contain any data.
|
||||
pub fn apply_cell_data_changeset<C: ToString, T: AsRef<FieldRevision>>(
|
||||
changeset: C,
|
||||
cell_rev: Option<CellRevision>,
|
||||
@ -114,14 +118,16 @@ pub fn try_decode_cell_data(
|
||||
}
|
||||
}
|
||||
|
||||
/// If the cell data is not String type, it should impl this trait.
|
||||
/// Deserialize the String into cell specific data type.
|
||||
pub trait FromCellString {
|
||||
fn from_cell_str(s: &str) -> FlowyResult<Self>
|
||||
where
|
||||
Self: Sized;
|
||||
}
|
||||
|
||||
/// CellData is a helper struct. String will be parser into Option<T> only if the T impl the FromCellString trait.
|
||||
pub struct CellData<T>(pub Option<T>);
|
||||
|
||||
impl<T> CellData<T> {
|
||||
pub fn try_into_inner(self) -> FlowyResult<T> {
|
||||
match self.0 {
|
||||
@ -158,7 +164,8 @@ impl std::convert::From<CellData<String>> for String {
|
||||
}
|
||||
}
|
||||
|
||||
// CellChangeset
|
||||
/// If the changeset applying to the cell is not String type, it should impl this trait.
|
||||
/// Deserialize the string into cell specific changeset.
|
||||
pub trait FromCellChangeset {
|
||||
fn from_changeset(changeset: String) -> FlowyResult<Self>
|
||||
where
|
||||
|
@ -8,8 +8,7 @@ use crate::services::field::{default_type_option_builder_from_type, type_option_
|
||||
use crate::services::filter::{GridFilterChangeset, GridFilterService};
|
||||
use crate::services::persistence::block_index::BlockIndexCache;
|
||||
use crate::services::row::{
|
||||
make_grid_blocks, make_row_from_row_rev, make_row_rev_from_context, make_rows_from_row_revs,
|
||||
CreateRowRevisionBuilder, CreateRowRevisionPayload, GridBlockSnapshot,
|
||||
make_grid_blocks, make_row_from_row_rev, make_rows_from_row_revs, GridBlockSnapshot, RowRevisionBuilder,
|
||||
};
|
||||
use crate::services::setting::make_grid_setting;
|
||||
use bytes::Bytes;
|
||||
@ -274,8 +273,7 @@ impl GridRevisionEditor {
|
||||
let block_id = self.block_id().await?;
|
||||
|
||||
// insert empty row below the row whose id is upper_row_id
|
||||
let row_rev_ctx = CreateRowRevisionBuilder::new(&field_revs).build();
|
||||
let row_rev = make_row_rev_from_context(&block_id, row_rev_ctx);
|
||||
let row_rev = RowRevisionBuilder::new(&field_revs).build(&block_id);
|
||||
let row_order = RowInfo::from(&row_rev);
|
||||
|
||||
// insert the row
|
||||
@ -287,12 +285,11 @@ impl GridRevisionEditor {
|
||||
Ok(row_order)
|
||||
}
|
||||
|
||||
pub async fn insert_rows(&self, contexts: Vec<CreateRowRevisionPayload>) -> FlowyResult<Vec<RowInfo>> {
|
||||
pub async fn insert_rows(&self, row_revs: Vec<RowRevision>) -> FlowyResult<Vec<RowInfo>> {
|
||||
let block_id = self.block_id().await?;
|
||||
let mut rows_by_block_id: HashMap<String, Vec<RowRevision>> = HashMap::new();
|
||||
let mut row_orders = vec![];
|
||||
for ctx in contexts {
|
||||
let row_rev = make_row_rev_from_context(&block_id, ctx);
|
||||
for row_rev in row_revs {
|
||||
row_orders.push(RowInfo::from(&row_rev));
|
||||
rows_by_block_id
|
||||
.entry(block_id.clone())
|
||||
@ -307,10 +304,7 @@ impl GridRevisionEditor {
|
||||
}
|
||||
|
||||
pub async fn update_row(&self, changeset: RowMetaChangeset) -> FlowyResult<()> {
|
||||
let field_revs = self.get_field_revs(None).await?;
|
||||
self.block_manager
|
||||
.update_row(changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev))
|
||||
.await
|
||||
self.block_manager.update_row(changeset, make_row_from_row_rev).await
|
||||
}
|
||||
|
||||
pub async fn get_rows(&self, block_id: &str) -> FlowyResult<RepeatedRow> {
|
||||
@ -322,26 +316,20 @@ impl GridRevisionEditor {
|
||||
debug_assert_eq!(grid_block_snapshot.len(), 1);
|
||||
if grid_block_snapshot.len() == 1 {
|
||||
let snapshot = grid_block_snapshot.pop().unwrap();
|
||||
let field_revs = self.get_field_revs(None).await?;
|
||||
let rows = make_rows_from_row_revs(&field_revs, &snapshot.row_revs);
|
||||
let rows = make_rows_from_row_revs(&snapshot.row_revs);
|
||||
Ok(rows.into())
|
||||
} else {
|
||||
Ok(vec![].into())
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_row(&self, row_id: &str) -> FlowyResult<Option<Row>> {
|
||||
pub async fn get_row_rev(&self, row_id: &str) -> FlowyResult<Option<Arc<RowRevision>>> {
|
||||
match self.block_manager.get_row_rev(row_id).await? {
|
||||
None => Ok(None),
|
||||
Some(row_rev) => {
|
||||
let field_revs = self.get_field_revs(None).await?;
|
||||
let row_revs = vec![row_rev];
|
||||
let mut rows = make_rows_from_row_revs(&field_revs, &row_revs);
|
||||
debug_assert!(rows.len() == 1);
|
||||
Ok(rows.pop())
|
||||
}
|
||||
Some(row_rev) => Ok(Some(row_rev)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn delete_row(&self, row_id: &str) -> FlowyResult<()> {
|
||||
let _ = self.block_manager.delete_row(row_id).await?;
|
||||
Ok(())
|
||||
@ -360,6 +348,10 @@ impl GridRevisionEditor {
|
||||
Some(Cell::new(¶ms.field_id, data))
|
||||
}
|
||||
|
||||
pub async fn get_cell_display(&self, _params: &CellIdentifier) -> Option<String> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub async fn get_cell_rev(&self, row_id: &str, field_id: &str) -> FlowyResult<Option<CellRevision>> {
|
||||
let row_rev = self.block_manager.get_row_rev(row_id).await?;
|
||||
match row_rev {
|
||||
@ -395,7 +387,6 @@ impl GridRevisionEditor {
|
||||
let cell_rev = self.get_cell_rev(&row_id, &field_id).await?;
|
||||
// Update the changeset.data property with the return value.
|
||||
content = Some(apply_cell_data_changeset(content.unwrap(), cell_rev, field_rev)?);
|
||||
let field_revs = self.get_field_revs(None).await?;
|
||||
let cell_changeset = CellChangeset {
|
||||
grid_id,
|
||||
row_id,
|
||||
@ -404,7 +395,7 @@ impl GridRevisionEditor {
|
||||
};
|
||||
let _ = self
|
||||
.block_manager
|
||||
.update_cell(cell_changeset, |row_rev| make_row_from_row_rev(&field_revs, row_rev))
|
||||
.update_cell(cell_changeset, make_row_from_row_rev)
|
||||
.await?;
|
||||
Ok(())
|
||||
}
|
||||
|
@ -6,12 +6,12 @@ use indexmap::IndexMap;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct CreateRowRevisionBuilder<'a> {
|
||||
pub struct RowRevisionBuilder<'a> {
|
||||
field_rev_map: HashMap<&'a String, &'a Arc<FieldRevision>>,
|
||||
payload: CreateRowRevisionPayload,
|
||||
}
|
||||
|
||||
impl<'a> CreateRowRevisionBuilder<'a> {
|
||||
impl<'a> RowRevisionBuilder<'a> {
|
||||
pub fn new(fields: &'a [Arc<FieldRevision>]) -> Self {
|
||||
let field_rev_map = fields
|
||||
.iter()
|
||||
@ -28,10 +28,10 @@ impl<'a> CreateRowRevisionBuilder<'a> {
|
||||
Self { field_rev_map, payload }
|
||||
}
|
||||
|
||||
pub fn add_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
|
||||
pub fn insert_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
|
||||
match self.field_rev_map.get(&field_id.to_owned()) {
|
||||
None => {
|
||||
let msg = format!("Invalid field_id: {}", field_id);
|
||||
let msg = format!("Can't find the field with id: {}", field_id);
|
||||
Err(FlowyError::internal().context(msg))
|
||||
}
|
||||
Some(field_rev) => {
|
||||
@ -43,7 +43,7 @@ impl<'a> CreateRowRevisionBuilder<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn add_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
|
||||
pub fn insert_select_option_cell(&mut self, field_id: &str, data: String) -> FlowyResult<()> {
|
||||
match self.field_rev_map.get(&field_id.to_owned()) {
|
||||
None => {
|
||||
let msg = format!("Invalid field_id: {}", field_id);
|
||||
@ -71,18 +71,14 @@ impl<'a> CreateRowRevisionBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
pub fn build(self) -> CreateRowRevisionPayload {
|
||||
self.payload
|
||||
}
|
||||
}
|
||||
|
||||
pub fn make_row_rev_from_context(block_id: &str, payload: CreateRowRevisionPayload) -> RowRevision {
|
||||
RowRevision {
|
||||
id: payload.row_id,
|
||||
block_id: block_id.to_owned(),
|
||||
cells: payload.cell_by_field_id,
|
||||
height: payload.height,
|
||||
visibility: payload.visibility,
|
||||
pub fn build(self, block_id: &str) -> RowRevision {
|
||||
RowRevision {
|
||||
id: self.payload.row_id,
|
||||
block_id: block_id.to_owned(),
|
||||
cells: self.payload.cell_by_field_id,
|
||||
height: self.payload.height,
|
||||
visibility: self.payload.visibility,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
use crate::entities::{GridBlock, RepeatedGridBlock, Row, RowInfo};
|
||||
use flowy_error::FlowyResult;
|
||||
use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
|
||||
use flowy_grid_data_model::revision::RowRevision;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
@ -39,28 +39,14 @@ pub(crate) fn make_row_orders_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Ve
|
||||
row_revs.iter().map(RowInfo::from).collect::<Vec<_>>()
|
||||
}
|
||||
|
||||
pub(crate) fn make_row_from_row_rev(fields: &[Arc<FieldRevision>], row_rev: Arc<RowRevision>) -> Option<Row> {
|
||||
make_rows_from_row_revs(fields, &[row_rev]).pop()
|
||||
pub(crate) fn make_row_from_row_rev(row_rev: Arc<RowRevision>) -> Option<Row> {
|
||||
make_rows_from_row_revs(&[row_rev]).pop()
|
||||
}
|
||||
|
||||
pub(crate) fn make_rows_from_row_revs(_fields: &[Arc<FieldRevision>], row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
|
||||
// let field_rev_map = fields
|
||||
// .iter()
|
||||
// .map(|field_rev| (&field_rev.id, field_rev))
|
||||
// .collect::<HashMap<&String, &FieldRevision>>();
|
||||
|
||||
let make_row = |row_rev: &Arc<RowRevision>| {
|
||||
// let cell_by_field_id = row_rev
|
||||
// .cells
|
||||
// .clone()
|
||||
// .into_iter()
|
||||
// .flat_map(|(field_id, cell_rev)| make_cell_by_field_id(&field_rev_map, field_id, cell_rev))
|
||||
// .collect::<HashMap<String, Cell>>();
|
||||
|
||||
Row {
|
||||
id: row_rev.id.clone(),
|
||||
height: row_rev.height,
|
||||
}
|
||||
pub(crate) fn make_rows_from_row_revs(row_revs: &[Arc<RowRevision>]) -> Vec<Row> {
|
||||
let make_row = |row_rev: &Arc<RowRevision>| Row {
|
||||
id: row_rev.id.clone(),
|
||||
height: row_rev.height,
|
||||
};
|
||||
|
||||
row_revs.iter().map(make_row).collect::<Vec<_>>()
|
||||
|
@ -4,29 +4,29 @@ use flowy_grid_data_model::revision::BuildGridContext;
|
||||
use flowy_sync::client_grid::GridBuilder;
|
||||
|
||||
pub fn make_default_grid() -> BuildGridContext {
|
||||
let mut grid_builder = GridBuilder::new();
|
||||
// text
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.primary(true)
|
||||
.build();
|
||||
grid_builder.add_field(text_field);
|
||||
|
||||
// single select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default();
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Type").visibility(true).build();
|
||||
grid_builder.add_field(single_select_field);
|
||||
|
||||
// checkbox
|
||||
let checkbox_field = FieldBuilder::from_field_type(&FieldType::Checkbox)
|
||||
.name("Done")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
|
||||
GridBuilder::default()
|
||||
.add_field(text_field)
|
||||
.add_field(single_select_field)
|
||||
.add_field(checkbox_field)
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.build()
|
||||
grid_builder.add_empty_row();
|
||||
grid_builder.add_empty_row();
|
||||
grid_builder.add_empty_row();
|
||||
grid_builder.build()
|
||||
}
|
||||
|
@ -1,13 +1,5 @@
|
||||
use crate::grid::block_test::script::GridRowTest;
|
||||
use crate::grid::block_test::script::RowScript::*;
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use chrono::NaiveDateTime;
|
||||
use flowy_grid::entities::FieldType;
|
||||
use flowy_grid::services::cell::decode_any_cell_data;
|
||||
use flowy_grid::services::field::select_option::SELECTION_IDS_SEPARATOR;
|
||||
use flowy_grid::services::field::{DateCellData, MultiSelectTypeOption, SingleSelectTypeOption};
|
||||
|
||||
use crate::grid::field_test::util::make_date_cell_string;
|
||||
use flowy_grid_data_model::revision::RowMetaChangeset;
|
||||
|
||||
#[tokio::test]
|
||||
@ -18,7 +10,7 @@ async fn grid_create_row_count_test() {
|
||||
CreateEmptyRow,
|
||||
CreateEmptyRow,
|
||||
CreateRow {
|
||||
payload: GridRowTestBuilder::new(&test).build(),
|
||||
row_rev: test.row_builder().build(),
|
||||
},
|
||||
AssertRowCount(6),
|
||||
];
|
||||
@ -28,15 +20,15 @@ async fn grid_create_row_count_test() {
|
||||
#[tokio::test]
|
||||
async fn grid_update_row() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let payload = GridRowTestBuilder::new(&test).build();
|
||||
let row_rev = test.row_builder().build();
|
||||
let changeset = RowMetaChangeset {
|
||||
row_id: payload.row_id.clone(),
|
||||
row_id: row_rev.id.clone(),
|
||||
height: None,
|
||||
visibility: None,
|
||||
cell_by_field_id: Default::default(),
|
||||
};
|
||||
|
||||
let scripts = vec![AssertRowCount(3), CreateRow { payload }, UpdateRow { changeset }];
|
||||
let scripts = vec![AssertRowCount(3), CreateRow { row_rev }, UpdateRow { changeset }];
|
||||
test.run_scripts(scripts).await;
|
||||
|
||||
let expected_row = test.last_row().unwrap();
|
||||
@ -47,13 +39,13 @@ async fn grid_update_row() {
|
||||
#[tokio::test]
|
||||
async fn grid_delete_row() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let payload1 = GridRowTestBuilder::new(&test).build();
|
||||
let payload2 = GridRowTestBuilder::new(&test).build();
|
||||
let row_ids = vec![payload1.row_id.clone(), payload2.row_id.clone()];
|
||||
let row_1 = test.row_builder().build();
|
||||
let row_2 = test.row_builder().build();
|
||||
let row_ids = vec![row_1.id.clone(), row_2.id.clone()];
|
||||
let scripts = vec![
|
||||
AssertRowCount(3),
|
||||
CreateRow { payload: payload1 },
|
||||
CreateRow { payload: payload2 },
|
||||
CreateRow { row_rev: row_1 },
|
||||
CreateRow { row_rev: row_2 },
|
||||
AssertBlockCount(1),
|
||||
AssertBlock {
|
||||
block_index: 0,
|
||||
@ -73,78 +65,17 @@ async fn grid_delete_row() {
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_cells_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut builder = test.builder();
|
||||
for field in test.field_revs() {
|
||||
let field_type: FieldType = field.field_type_rev.into();
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
builder.add_cell(&field.id, "hello world".to_owned()).unwrap();
|
||||
}
|
||||
FieldType::Number => {
|
||||
builder.add_cell(&field.id, "18,443".to_owned()).unwrap();
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
builder
|
||||
.add_cell(&field.id, make_date_cell_string("1647251762"))
|
||||
.unwrap();
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
let type_option = SingleSelectTypeOption::from(field);
|
||||
let option = type_option.options.first().unwrap();
|
||||
builder.add_select_option_cell(&field.id, option.id.clone()).unwrap();
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
let type_option = MultiSelectTypeOption::from(field);
|
||||
let ops_ids = type_option
|
||||
.options
|
||||
.iter()
|
||||
.map(|option| option.id.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(SELECTION_IDS_SEPARATOR);
|
||||
builder.add_select_option_cell(&field.id, ops_ids).unwrap();
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
builder.add_cell(&field.id, "false".to_string()).unwrap();
|
||||
}
|
||||
FieldType::URL => {
|
||||
builder.add_cell(&field.id, "1".to_string()).unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let scripts = vec![CreateRow { payload: context }];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
let mut builder = test.row_builder();
|
||||
|
||||
#[tokio::test]
|
||||
async fn grid_row_add_date_cell_test() {
|
||||
let mut test = GridRowTest::new().await;
|
||||
let mut builder = test.builder();
|
||||
let mut date_field = None;
|
||||
let timestamp = 1647390674;
|
||||
for field in test.field_revs() {
|
||||
let field_type: FieldType = field.field_type_rev.into();
|
||||
if field_type == FieldType::DateTime {
|
||||
date_field = Some(field.clone());
|
||||
NaiveDateTime::from_timestamp(123, 0);
|
||||
// The data should not be empty
|
||||
assert!(builder.add_cell(&field.id, "".to_string()).is_err());
|
||||
assert!(builder.add_cell(&field.id, make_date_cell_string("123")).is_ok());
|
||||
assert!(builder
|
||||
.add_cell(&field.id, make_date_cell_string(×tamp.to_string()))
|
||||
.is_ok());
|
||||
}
|
||||
}
|
||||
let context = builder.build();
|
||||
let date_field = date_field.unwrap();
|
||||
let cell_rev = context.cell_by_field_id.get(&date_field.id).unwrap();
|
||||
assert_eq!(
|
||||
decode_any_cell_data(cell_rev, &date_field)
|
||||
.parse::<DateCellData>()
|
||||
.unwrap()
|
||||
.date,
|
||||
"2022/03/16",
|
||||
);
|
||||
let scripts = vec![CreateRow { payload: context }];
|
||||
builder.insert_text_cell("hello world");
|
||||
builder.insert_number_cell("18,443");
|
||||
builder.insert_date_cell("1647251762");
|
||||
builder.insert_single_select_cell(|options| options.first().unwrap());
|
||||
builder.insert_multi_select_cell(|options| options);
|
||||
builder.insert_checkbox_cell("false");
|
||||
builder.insert_url_cell("1");
|
||||
|
||||
let row_rev = builder.build();
|
||||
let scripts = vec![CreateRow { row_rev }];
|
||||
test.run_scripts(scripts).await;
|
||||
}
|
||||
|
@ -1,15 +1,16 @@
|
||||
use crate::grid::block_test::util::GridRowTestBuilder;
|
||||
use crate::grid::grid_editor::GridEditorTest;
|
||||
use flowy_grid::entities::RowInfo;
|
||||
use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
|
||||
use flowy_grid::entities::{CellIdentifier, RowInfo};
|
||||
|
||||
use flowy_grid_data_model::revision::{
|
||||
FieldRevision, GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
|
||||
GridBlockMetaRevision, GridBlockMetaRevisionChangeset, RowMetaChangeset, RowRevision,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub enum RowScript {
|
||||
CreateEmptyRow,
|
||||
CreateRow {
|
||||
payload: CreateRowRevisionPayload,
|
||||
row_rev: RowRevision,
|
||||
},
|
||||
UpdateRow {
|
||||
changeset: RowMetaChangeset,
|
||||
@ -20,6 +21,11 @@ pub enum RowScript {
|
||||
DeleteRows {
|
||||
row_ids: Vec<String>,
|
||||
},
|
||||
AssertCell {
|
||||
row_id: String,
|
||||
field_id: String,
|
||||
expected_display: Option<String>,
|
||||
},
|
||||
AssertRowCount(usize),
|
||||
CreateBlock {
|
||||
block: GridBlockMetaRevision,
|
||||
@ -49,10 +55,6 @@ impl GridRowTest {
|
||||
Self { inner: editor_test }
|
||||
}
|
||||
|
||||
pub fn field_revs(&self) -> &Vec<Arc<FieldRevision>> {
|
||||
&self.field_revs
|
||||
}
|
||||
|
||||
pub fn last_row(&self) -> Option<RowRevision> {
|
||||
self.row_revs.last().map(|a| a.clone().as_ref().clone())
|
||||
}
|
||||
@ -63,8 +65,8 @@ impl GridRowTest {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn builder(&self) -> CreateRowRevisionBuilder {
|
||||
CreateRowRevisionBuilder::new(&self.field_revs)
|
||||
pub fn row_builder(&self) -> GridRowTestBuilder {
|
||||
GridRowTestBuilder::new(self.block_id(), &self.field_revs)
|
||||
}
|
||||
|
||||
pub async fn run_script(&mut self, script: RowScript) {
|
||||
@ -76,8 +78,8 @@ impl GridRowTest {
|
||||
self.row_revs = self.get_row_revs().await;
|
||||
self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
|
||||
}
|
||||
RowScript::CreateRow { payload: context } => {
|
||||
let row_orders = self.editor.insert_rows(vec![context]).await.unwrap();
|
||||
RowScript::CreateRow { row_rev } => {
|
||||
let row_orders = self.editor.insert_rows(vec![row_rev]).await.unwrap();
|
||||
for row_order in row_orders {
|
||||
self.row_order_by_row_id
|
||||
.insert(row_order.row_id().to_owned(), row_order);
|
||||
@ -96,6 +98,24 @@ impl GridRowTest {
|
||||
self.row_revs = self.get_row_revs().await;
|
||||
self.block_meta_revs = self.editor.get_block_meta_revs().await.unwrap();
|
||||
}
|
||||
RowScript::AssertCell {
|
||||
row_id,
|
||||
field_id,
|
||||
expected_display,
|
||||
} => {
|
||||
let id = CellIdentifier {
|
||||
grid_id: self.grid_id.clone(),
|
||||
field_id,
|
||||
row_id,
|
||||
};
|
||||
let display = self.editor.get_cell_display(&id).await;
|
||||
match expected_display {
|
||||
None => {}
|
||||
Some(expected_display) => {
|
||||
assert_eq!(display.unwrap(), expected_display);
|
||||
}
|
||||
}
|
||||
}
|
||||
RowScript::AssertRow { expected_row } => {
|
||||
let row = &*self
|
||||
.row_revs
|
||||
|
@ -1,66 +1,97 @@
|
||||
use crate::grid::block_test::script::GridRowTest;
|
||||
|
||||
use flowy_grid::entities::FieldType;
|
||||
use flowy_grid::services::field::DateCellChangeset;
|
||||
use flowy_grid::services::row::{CreateRowRevisionBuilder, CreateRowRevisionPayload};
|
||||
use flowy_grid_data_model::revision::FieldRevision;
|
||||
use flowy_grid::services::field::select_option::{SelectOption, SELECTION_IDS_SEPARATOR};
|
||||
use flowy_grid::services::field::{DateCellChangeset, MultiSelectTypeOption, SingleSelectTypeOption};
|
||||
use flowy_grid::services::row::RowRevisionBuilder;
|
||||
use flowy_grid_data_model::revision::{FieldRevision, RowRevision};
|
||||
use std::sync::Arc;
|
||||
use strum::EnumCount;
|
||||
|
||||
pub struct GridRowTestBuilder<'a> {
|
||||
test: &'a GridRowTest,
|
||||
inner_builder: CreateRowRevisionBuilder<'a>,
|
||||
block_id: String,
|
||||
field_revs: &'a [Arc<FieldRevision>],
|
||||
inner_builder: RowRevisionBuilder<'a>,
|
||||
}
|
||||
|
||||
impl<'a> GridRowTestBuilder<'a> {
|
||||
pub fn new(test: &'a GridRowTest) -> Self {
|
||||
assert_eq!(test.field_revs().len(), FieldType::COUNT);
|
||||
|
||||
let inner_builder = CreateRowRevisionBuilder::new(test.field_revs());
|
||||
Self { test, inner_builder }
|
||||
}
|
||||
#[allow(dead_code)]
|
||||
pub fn update_text_cell(mut self, data: String) -> Self {
|
||||
let text_field = self.field_rev_with_type(&FieldType::DateTime);
|
||||
self.inner_builder.add_cell(&text_field.id, data).unwrap();
|
||||
self
|
||||
pub fn new(block_id: &str, field_revs: &'a [Arc<FieldRevision>]) -> Self {
|
||||
assert_eq!(field_revs.len(), FieldType::COUNT);
|
||||
let inner_builder = RowRevisionBuilder::new(field_revs);
|
||||
Self {
|
||||
block_id: block_id.to_owned(),
|
||||
field_revs,
|
||||
inner_builder,
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_number_cell(mut self, data: String) -> Self {
|
||||
let number_field = self.field_rev_with_type(&FieldType::DateTime);
|
||||
self.inner_builder.add_cell(&number_field.id, data).unwrap();
|
||||
self
|
||||
pub fn insert_text_cell(&mut self, data: &str) {
|
||||
let text_field = self.field_rev_with_type(&FieldType::RichText);
|
||||
self.inner_builder
|
||||
.insert_cell(&text_field.id, data.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_date_cell(mut self, value: i64) -> Self {
|
||||
pub fn insert_number_cell(&mut self, data: &str) {
|
||||
let number_field = self.field_rev_with_type(&FieldType::Number);
|
||||
self.inner_builder
|
||||
.insert_cell(&number_field.id, data.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn insert_date_cell(&mut self, data: &str) {
|
||||
let value = serde_json::to_string(&DateCellChangeset {
|
||||
date: Some(value.to_string()),
|
||||
date: Some(data.to_string()),
|
||||
time: None,
|
||||
})
|
||||
.unwrap();
|
||||
let date_field = self.field_rev_with_type(&FieldType::DateTime);
|
||||
self.inner_builder.add_cell(&date_field.id, value).unwrap();
|
||||
self
|
||||
self.inner_builder.insert_cell(&date_field.id, value).unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_checkbox_cell(mut self, data: bool) -> Self {
|
||||
pub fn insert_checkbox_cell(&mut self, data: &str) {
|
||||
let number_field = self.field_rev_with_type(&FieldType::Checkbox);
|
||||
self.inner_builder.add_cell(&number_field.id, data.to_string()).unwrap();
|
||||
self
|
||||
self.inner_builder
|
||||
.insert_cell(&number_field.id, data.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub fn update_url_cell(mut self, data: String) -> Self {
|
||||
let number_field = self.field_rev_with_type(&FieldType::Checkbox);
|
||||
self.inner_builder.add_cell(&number_field.id, data).unwrap();
|
||||
self
|
||||
pub fn insert_url_cell(&mut self, data: &str) {
|
||||
let number_field = self.field_rev_with_type(&FieldType::URL);
|
||||
self.inner_builder
|
||||
.insert_cell(&number_field.id, data.to_string())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn insert_single_select_cell<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&Vec<SelectOption>) -> &SelectOption,
|
||||
{
|
||||
let single_select_field = self.field_rev_with_type(&FieldType::SingleSelect);
|
||||
let type_option = SingleSelectTypeOption::from(&single_select_field);
|
||||
let option = f(&type_option.options);
|
||||
self.inner_builder
|
||||
.insert_select_option_cell(&single_select_field.id, option.id.clone())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn insert_multi_select_cell<F>(&mut self, f: F)
|
||||
where
|
||||
F: Fn(&Vec<SelectOption>) -> &Vec<SelectOption>,
|
||||
{
|
||||
let multi_select_field = self.field_rev_with_type(&FieldType::MultiSelect);
|
||||
let type_option = MultiSelectTypeOption::from(&multi_select_field);
|
||||
let options = f(&type_option.options);
|
||||
let ops_ids = options
|
||||
.iter()
|
||||
.map(|option| option.id.clone())
|
||||
.collect::<Vec<_>>()
|
||||
.join(SELECTION_IDS_SEPARATOR);
|
||||
self.inner_builder
|
||||
.insert_select_option_cell(&multi_select_field.id, ops_ids)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
pub fn field_rev_with_type(&self, field_type: &FieldType) -> FieldRevision {
|
||||
self.test
|
||||
.field_revs()
|
||||
self.field_revs
|
||||
.iter()
|
||||
.find(|field_rev| {
|
||||
let t_field_type: FieldType = field_rev.field_type_rev.into();
|
||||
@ -71,7 +102,7 @@ impl<'a> GridRowTestBuilder<'a> {
|
||||
.clone()
|
||||
}
|
||||
|
||||
pub fn build(self) -> CreateRowRevisionPayload {
|
||||
self.inner_builder.build()
|
||||
pub fn build(self) -> RowRevision {
|
||||
self.inner_builder.build(&self.block_id)
|
||||
}
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ use flowy_grid::entities::*;
|
||||
use flowy_grid::services::field::select_option::SelectOption;
|
||||
use flowy_grid::services::field::*;
|
||||
use flowy_grid::services::grid_editor::{GridPadBuilder, GridRevisionEditor};
|
||||
use flowy_grid::services::row::CreateRowRevisionPayload;
|
||||
use flowy_grid::services::row::{CreateRowRevisionPayload, RowRevisionBuilder};
|
||||
use flowy_grid::services::setting::GridSettingChangesetBuilder;
|
||||
use flowy_grid_data_model::revision::*;
|
||||
use flowy_revision::REVISION_WRITE_INTERVAL_IN_MILLIS;
|
||||
@ -20,6 +20,7 @@ use std::collections::HashMap;
|
||||
use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
use strum::EnumCount;
|
||||
use strum::IntoEnumIterator;
|
||||
use tokio::time::sleep;
|
||||
|
||||
pub struct GridEditorTest {
|
||||
@ -37,14 +38,13 @@ impl GridEditorTest {
|
||||
pub async fn new() -> Self {
|
||||
let sdk = FlowySDKTest::default();
|
||||
let _ = sdk.init_user().await;
|
||||
let build_context = make_all_field_test_grid();
|
||||
let build_context = make_test_grid();
|
||||
let view_data: Bytes = build_context.into();
|
||||
let test = ViewTest::new_grid_view(&sdk, view_data.to_vec()).await;
|
||||
let editor = sdk.grid_manager.open_grid(&test.view.id).await.unwrap();
|
||||
let field_revs = editor.get_field_revs(None).await.unwrap();
|
||||
let block_meta_revs = editor.get_block_meta_revs().await.unwrap();
|
||||
let row_revs = editor.grid_block_snapshots(None).await.unwrap().pop().unwrap().row_revs;
|
||||
assert_eq!(row_revs.len(), 3);
|
||||
assert_eq!(block_meta_revs.len(), 1);
|
||||
|
||||
// It seems like you should add the field in the make_test_grid() function.
|
||||
@ -90,75 +90,98 @@ impl GridEditorTest {
|
||||
.pop()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn block_id(&self) -> &str {
|
||||
&self.block_meta_revs.last().unwrap().block_id
|
||||
}
|
||||
}
|
||||
|
||||
fn make_all_field_test_grid() -> BuildGridContext {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
// This grid is assumed to contain all the Fields.
|
||||
fn make_test_grid() -> BuildGridContext {
|
||||
let mut grid_builder = GridBuilder::new();
|
||||
// Iterate through the FieldType to create the corresponding Field.
|
||||
for field_type in FieldType::iter() {
|
||||
let field_type: FieldType = field_type;
|
||||
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Live"))
|
||||
.option(SelectOption::new("Completed"))
|
||||
.option(SelectOption::new("Planned"))
|
||||
.option(SelectOption::new("Paused"));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
// The
|
||||
match field_type {
|
||||
FieldType::RichText => {
|
||||
let text_field = FieldBuilder::new(RichTextTypeOptionBuilder::default())
|
||||
.name("Name")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(text_field);
|
||||
}
|
||||
FieldType::Number => {
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
grid_builder.add_field(number_field);
|
||||
}
|
||||
FieldType::DateTime => {
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
grid_builder.add_field(date_field);
|
||||
}
|
||||
FieldType::SingleSelect => {
|
||||
// Single Select
|
||||
let single_select = SingleSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Live"))
|
||||
.option(SelectOption::new("Completed"))
|
||||
.option(SelectOption::new("Planned"))
|
||||
.option(SelectOption::new("Paused"));
|
||||
let single_select_field = FieldBuilder::new(single_select).name("Status").visibility(true).build();
|
||||
grid_builder.add_field(single_select_field);
|
||||
}
|
||||
FieldType::MultiSelect => {
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Google"))
|
||||
.option(SelectOption::new("Facebook"))
|
||||
.option(SelectOption::new("Twitter"));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
grid_builder.add_field(multi_select_field);
|
||||
}
|
||||
FieldType::Checkbox => {
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build();
|
||||
grid_builder.add_field(checkbox_field);
|
||||
}
|
||||
FieldType::URL => {
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
grid_builder.add_field(url_field);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MultiSelect
|
||||
let multi_select = MultiSelectTypeOptionBuilder::default()
|
||||
.option(SelectOption::new("Google"))
|
||||
.option(SelectOption::new("Facebook"))
|
||||
.option(SelectOption::new("Twitter"));
|
||||
let multi_select_field = FieldBuilder::new(multi_select)
|
||||
.name("Platform")
|
||||
.visibility(true)
|
||||
.build();
|
||||
|
||||
// Number
|
||||
let number = NumberTypeOptionBuilder::default().set_format(NumberFormat::USD);
|
||||
let number_field = FieldBuilder::new(number).name("Price").visibility(true).build();
|
||||
|
||||
// Date
|
||||
let date = DateTypeOptionBuilder::default()
|
||||
.date_format(DateFormat::US)
|
||||
.time_format(TimeFormat::TwentyFourHour);
|
||||
let date_field = FieldBuilder::new(date).name("Time").visibility(true).build();
|
||||
|
||||
// Checkbox
|
||||
let checkbox = CheckboxTypeOptionBuilder::default();
|
||||
let checkbox_field = FieldBuilder::new(checkbox).name("is done").visibility(true).build();
|
||||
|
||||
// URL
|
||||
let url = URLTypeOptionBuilder::default();
|
||||
let url_field = FieldBuilder::new(url).name("link").visibility(true).build();
|
||||
|
||||
// for i in 0..3 {
|
||||
// for field_type in FieldType::iter() {
|
||||
// let field_type: FieldType = field_type;
|
||||
// match field_type {
|
||||
// FieldType::RichText => {}
|
||||
// FieldType::Number => {}
|
||||
// FieldType::DateTime => {}
|
||||
// FieldType::SingleSelect => {}
|
||||
// FieldType::MultiSelect => {}
|
||||
// FieldType::Checkbox => {}
|
||||
// FieldType::URL => {}
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
GridBuilder::default()
|
||||
.add_field(text_field)
|
||||
.add_field(single_select_field)
|
||||
.add_field(multi_select_field)
|
||||
.add_field(number_field)
|
||||
.add_field(date_field)
|
||||
.add_field(checkbox_field)
|
||||
.add_field(url_field)
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.add_empty_row()
|
||||
.build()
|
||||
// We have many assumptions base on the number of the rows, so do not change the number of the loop.
|
||||
for _i in 0..10 {
|
||||
for field_type in FieldType::iter() {
|
||||
let field_type: FieldType = field_type;
|
||||
// let mut row_builder = RowRevisionBuilder::new()
|
||||
match field_type {
|
||||
FieldType::RichText => {}
|
||||
FieldType::Number => {}
|
||||
FieldType::DateTime => {}
|
||||
FieldType::SingleSelect => {}
|
||||
FieldType::MultiSelect => {}
|
||||
FieldType::Checkbox => {}
|
||||
FieldType::URL => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
// assert_eq!(row_revs.len(), 10);
|
||||
// .add_empty_row()
|
||||
// .add_empty_row()
|
||||
// .add_empty_row()
|
||||
grid_builder.build()
|
||||
}
|
||||
|
@ -26,18 +26,23 @@ impl std::default::Default for GridBuilder {
|
||||
}
|
||||
|
||||
impl GridBuilder {
|
||||
pub fn add_field(mut self, field: FieldRevision) -> Self {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn add_field(&mut self, field: FieldRevision) {
|
||||
self.build_context.field_revs.push(field);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_empty_row(mut self) -> Self {
|
||||
let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id);
|
||||
pub fn add_row(&mut self, row_rev: RowRevision) {
|
||||
let block_meta_rev = self.build_context.blocks.first_mut().unwrap();
|
||||
let block_rev = self.build_context.blocks_meta_data.first_mut().unwrap();
|
||||
block_rev.rows.push(Arc::new(row));
|
||||
block_rev.rows.push(Arc::new(row_rev));
|
||||
block_meta_rev.row_count += 1;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn add_empty_row(&mut self) {
|
||||
let row = RowRevision::new(&self.build_context.blocks.first().unwrap().block_id);
|
||||
self.add_row(row);
|
||||
}
|
||||
|
||||
pub fn build(self) -> BuildGridContext {
|
||||
|
Loading…
Reference in New Issue
Block a user