Feat/read cell data for field (#1695)

* chore: read cells for field

* feat: enable read cells for specific field

* ci: fix tests

Co-authored-by: vedon <vedon.fu@gmail.com>
Co-authored-by: nathan <nathan@appflowy.io>
This commit is contained in:
Nathan.fooo 2023-01-12 07:56:46 +08:00 committed by GitHub
parent b7ba189642
commit fe4e28b576
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 172 additions and 87 deletions

View File

@ -42,7 +42,7 @@ packages:
path: "packages/appflowy_editor"
relative: true
source: path
version: "0.0.7"
version: "0.0.9"
appflowy_editor_plugins:
dependency: "direct main"
description:

View File

@ -1,67 +1,73 @@
import 'package:app_flowy/plugins/document/document.dart';
import 'package:app_flowy/workspace/application/app/app_bloc.dart';
import 'package:app_flowy/workspace/application/view/view_bloc.dart';
import 'package:bloc_test/bloc_test.dart';
import 'package:flutter_test/flutter_test.dart';
import '../../util.dart';
void main() {
late AppFlowyUnitTest test;
late AppFlowyUnitTest testContext;
setUpAll(() async {
test = await AppFlowyUnitTest.ensureInitialized();
testContext = await AppFlowyUnitTest.ensureInitialized();
});
group('$ViewBloc', () {
late AppBloc appBloc;
test('rename view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(app: app)..add(const AppEvent.initial());
appBloc.add(AppEvent.createView(
"Test document",
DocumentPluginBuilder(),
));
await blocResponseFuture();
setUpAll(() async {
final app = await test.createTestApp();
appBloc = AppBloc(app: app)..add(const AppEvent.initial());
appBloc.add(AppEvent.createView(
"Test document",
DocumentPluginBuilder(),
));
await blocResponseFuture();
});
final viewBloc = ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial());
viewBloc.add(const ViewEvent.rename('Hello world'));
await blocResponseFuture();
blocTest<ViewBloc, ViewState>(
"rename view",
build: () => ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial()),
act: (bloc) {
bloc.add(const ViewEvent.rename('Hello world'));
},
wait: blocResponseDuration(),
verify: (bloc) {
assert(bloc.state.view.name == "Hello world");
},
);
assert(viewBloc.state.view.name == "Hello world");
});
blocTest<ViewBloc, ViewState>(
"duplicate view",
build: () => ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial()),
act: (bloc) {
bloc.add(const ViewEvent.duplicate());
},
wait: blocResponseDuration(),
verify: (bloc) {
assert(appBloc.state.views.length == 2);
},
);
test('duplicate view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(app: app)..add(const AppEvent.initial());
await blocResponseFuture();
blocTest<ViewBloc, ViewState>(
"delete view",
build: () => ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial()),
act: (bloc) {
bloc.add(const ViewEvent.delete());
},
wait: blocResponseDuration(),
verify: (bloc) {
assert(appBloc.state.views.length == 1);
},
);
appBloc.add(AppEvent.createView(
"Test document",
DocumentPluginBuilder(),
));
await blocResponseFuture();
final viewBloc = ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial());
await blocResponseFuture();
viewBloc.add(const ViewEvent.duplicate());
await blocResponseFuture();
assert(appBloc.state.views.length == 2);
});
test('delete view test', () async {
final app = await testContext.createTestApp();
final appBloc = AppBloc(app: app)..add(const AppEvent.initial());
await blocResponseFuture();
appBloc.add(AppEvent.createView(
"Test document",
DocumentPluginBuilder(),
));
await blocResponseFuture();
assert(appBloc.state.views.length == 1);
final viewBloc = ViewBloc(view: appBloc.state.views.first)
..add(const ViewEvent.initial());
await blocResponseFuture();
viewBloc.add(const ViewEvent.delete());
await blocResponseFuture();
assert(appBloc.state.views.isEmpty);
});
}

View File

@ -228,6 +228,7 @@ impl GridBlockManager {
editor.get_row_rev(row_id).await
}
#[allow(dead_code)]
pub async fn get_row_revs(&self) -> FlowyResult<Vec<Arc<RowRevision>>> {
let mut row_revs = vec![];
for iter in self.block_editors.iter() {

View File

@ -9,7 +9,7 @@ use crate::services::cell::{
};
use crate::services::field::{
default_type_option_builder_from_type, transform_type_option, type_option_builder_from_bytes, FieldBuilder,
RowSingleCellData, TypeOptionCellExt,
RowSingleCellData,
};
use crate::services::filter::FilterType;
@ -76,6 +76,7 @@ impl GridRevisionEditor {
pad: grid_pad.clone(),
block_manager: block_manager.clone(),
task_scheduler,
cell_data_cache: cell_data_cache.clone(),
});
// View manager
@ -496,31 +497,10 @@ impl GridRevisionEditor {
}
}
pub async fn get_cell_data_for_field(&self, field_id: &str) -> FlowyResult<Vec<RowSingleCellData>> {
let row_revs = self.block_manager.get_row_revs().await?;
let field_rev = self.get_field_rev(field_id).await.unwrap();
let field_type: FieldType = field_rev.ty.into();
let mut cells = vec![];
if let Some(handler) =
TypeOptionCellExt::new_with_cell_data_cache(&field_rev, Some(self.cell_data_cache.clone()))
.get_type_option_cell_data_handler(&field_type)
{
for row_rev in row_revs {
if let Some(cell_rev) = row_rev.cells.get(field_id) {
if let Ok(type_cell_data) = TypeCellData::try_from(cell_rev) {
if let Ok(cell_data) = handler.get_cell_data(type_cell_data.cell_str, &field_type, &field_rev) {
cells.push(RowSingleCellData {
row_id: row_rev.id.clone(),
field_id: field_rev.id.clone(),
field_type: field_type.clone(),
cell_data,
})
}
}
}
}
}
Ok(cells)
/// Returns the list of cells corresponding to the given field.
pub async fn get_cells_for_field(&self, view_id: &str, field_id: &str) -> FlowyResult<Vec<RowSingleCellData>> {
let view_editor = self.view_manager.get_view_editor(view_id).await?;
view_editor.get_cells_for_field(field_id).await
}
#[tracing::instrument(level = "trace", skip_all, err)]

View File

@ -1,6 +1,10 @@
use crate::entities::FieldType;
use crate::services::block_manager::GridBlockManager;
use crate::services::cell::AtomicCellDataCache;
use crate::services::field::{TypeOptionCellDataHandler, TypeOptionCellExt};
use crate::services::row::GridBlockRowRevision;
use crate::services::view_editor::GridViewEditorDelegate;
use flowy_sync::client_grid::GridRevisionPad;
use flowy_task::TaskDispatcher;
use grid_rev_model::{FieldRevision, RowRevision};
@ -12,6 +16,7 @@ pub(crate) struct GridViewEditorDelegateImpl {
pub(crate) pad: Arc<RwLock<GridRevisionPad>>,
pub(crate) block_manager: Arc<GridBlockManager>,
pub(crate) task_scheduler: Arc<RwLock<TaskDispatcher>>,
pub(crate) cell_data_cache: AtomicCellDataCache,
}
impl GridViewEditorDelegate for GridViewEditorDelegateImpl {
@ -27,7 +32,6 @@ impl GridViewEditorDelegate for GridViewEditorDelegateImpl {
}
})
}
fn get_field_rev(&self, field_id: &str) -> Fut<Option<Arc<FieldRevision>>> {
let pad = self.pad.clone();
let field_id = field_id.to_owned();
@ -63,6 +67,10 @@ impl GridViewEditorDelegate for GridViewEditorDelegateImpl {
})
}
// /// Returns the list of cells corresponding to the given field.
// pub async fn get_cells_for_field(&self, field_id: &str) -> FlowyResult<Vec<RowSingleCellData>> {
// }
fn get_blocks(&self) -> Fut<Vec<GridBlockRowRevision>> {
let block_manager = self.block_manager.clone();
to_fut(async move { block_manager.get_blocks(None).await.unwrap_or_default() })
@ -71,4 +79,13 @@ impl GridViewEditorDelegate for GridViewEditorDelegateImpl {
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>> {
self.task_scheduler.clone()
}
fn get_type_option_cell_handler(
&self,
field_rev: &FieldRevision,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>> {
TypeOptionCellExt::new_with_cell_data_cache(field_rev, Some(self.cell_data_cache.clone()))
.get_type_option_cell_data_handler(field_type)
}
}

View File

@ -1,4 +1,5 @@
use crate::entities::{GroupPB, GroupViewChangesetPB};
use crate::services::field::RowSingleCellData;
use crate::services::group::{default_group_configuration, GeneratedGroupContext, Group};
use flowy_error::{FlowyError, FlowyResult};
use grid_rev_model::{
@ -13,6 +14,7 @@ use std::sync::Arc;
pub trait GroupConfigurationReader: Send + Sync + 'static {
fn get_configuration(&self) -> Fut<Option<Arc<GroupConfigurationRevision>>>;
fn get_configuration_cells(&self, field_id: &str) -> Fut<FlowyResult<Vec<RowSingleCellData>>>;
}
pub trait GroupConfigurationWriter: Send + Sync + 'static {
@ -53,6 +55,11 @@ pub struct GroupContext<C> {
/// Cache all the groups
groups_map: IndexMap<String, Group>,
/// A reader that implement the [GroupConfigurationReader] trait
///
#[allow(dead_code)]
reader: Arc<dyn GroupConfigurationReader>,
/// A writer that implement the [GroupConfigurationWriter] trait is used to save the
/// configuration to disk
///
@ -85,6 +92,7 @@ where
view_id,
field_rev,
groups_map: IndexMap::new(),
reader,
writer,
configuration,
configuration_phantom: PhantomData,

View File

@ -1,7 +1,8 @@
use crate::dart_notification::{send_dart_notification, GridDartNotification};
use crate::entities::*;
use crate::services::block_manager::GridBlockEvent;
use crate::services::cell::AtomicCellDataCache;
use crate::services::cell::{AtomicCellDataCache, TypeCellData};
use crate::services::field::{RowSingleCellData, TypeOptionCellDataHandler};
use crate::services::filter::{FilterChangeset, FilterController, FilterTaskHandler, FilterType, UpdatedFilterType};
use crate::services::group::{
default_group_configuration, find_group_field, make_group_controller, Group, GroupConfigurationReader,
@ -58,6 +59,12 @@ pub trait GridViewEditorDelegate: Send + Sync + 'static {
/// Returns a `TaskDispatcher` used to poll a `Task`
fn get_task_scheduler(&self) -> Arc<RwLock<TaskDispatcher>>;
fn get_type_option_cell_handler(
&self,
field_rev: &FieldRevision,
field_type: &FieldType,
) -> Option<Box<dyn TypeOptionCellDataHandler>>;
}
pub struct GridViewRevisionEditor {
@ -581,6 +588,10 @@ impl GridViewRevisionEditor {
pub async fn group_by_view_field(&self, field_id: &str) -> FlowyResult<()> {
if let Some(field_rev) = self.delegate.get_field_rev(field_id).await {
let row_revs = self.delegate.get_row_revs(None).await;
let configuration_reader = GroupConfigurationReaderImpl {
pad: self.pad.clone(),
view_editor_delegate: self.delegate.clone(),
};
let new_group_controller = new_group_controller_with_field_rev(
self.user_id.clone(),
self.view_id.clone(),
@ -588,6 +599,7 @@ impl GridViewRevisionEditor {
self.rev_manager.clone(),
field_rev,
row_revs,
configuration_reader,
)
.await?;
@ -614,6 +626,10 @@ impl GridViewRevisionEditor {
Ok(())
}
pub(crate) async fn get_cells_for_field(&self, field_id: &str) -> FlowyResult<Vec<RowSingleCellData>> {
get_cells_for_field(self.delegate.clone(), field_id).await
}
async fn notify_did_update_setting(&self) {
let setting = self.get_view_setting().await;
send_dart_notification(&self.view_id, GridDartNotification::DidUpdateGridSetting)
@ -691,6 +707,33 @@ impl GridViewRevisionEditor {
}
}
}
/// Returns the list of cells corresponding to the given field.
pub(crate) async fn get_cells_for_field(
delegate: Arc<dyn GridViewEditorDelegate>,
field_id: &str,
) -> FlowyResult<Vec<RowSingleCellData>> {
let row_revs = delegate.get_row_revs(None).await;
let field_rev = delegate.get_field_rev(field_id).await.unwrap();
let field_type: FieldType = field_rev.ty.into();
let mut cells = vec![];
if let Some(handler) = delegate.get_type_option_cell_handler(&field_rev, &field_type) {
for row_rev in row_revs {
if let Some(cell_rev) = row_rev.cells.get(field_id) {
if let Ok(type_cell_data) = TypeCellData::try_from(cell_rev) {
if let Ok(cell_data) = handler.get_cell_data(type_cell_data.cell_str, &field_type, &field_rev) {
cells.push(RowSingleCellData {
row_id: row_rev.id.clone(),
field_id: field_rev.id.clone(),
field_type: field_type.clone(),
cell_data,
})
}
}
}
}
}
Ok(cells)
}
#[async_trait]
impl RefCountValue for GridViewRevisionEditor {
@ -706,7 +749,10 @@ async fn new_group_controller(
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
delegate: Arc<dyn GridViewEditorDelegate>,
) -> FlowyResult<Box<dyn GroupController>> {
let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone());
let configuration_reader = GroupConfigurationReaderImpl {
pad: view_rev_pad.clone(),
view_editor_delegate: delegate.clone(),
};
let field_revs = delegate.get_field_revs(None).await;
let row_revs = delegate.get_row_revs(None).await;
let layout = view_rev_pad.read().await.layout();
@ -722,7 +768,16 @@ async fn new_group_controller(
})
.unwrap_or_else(|| find_group_field(&field_revs, &layout).unwrap());
new_group_controller_with_field_rev(user_id, view_id, view_rev_pad, rev_manager, field_rev, row_revs).await
new_group_controller_with_field_rev(
user_id,
view_id,
view_rev_pad,
rev_manager,
field_rev,
row_revs,
configuration_reader,
)
.await
}
/// Returns a [GroupController]
@ -734,8 +789,8 @@ async fn new_group_controller_with_field_rev(
rev_manager: Arc<RevisionManager<Arc<ConnectionPool>>>,
field_rev: Arc<FieldRevision>,
row_revs: Vec<Arc<RowRevision>>,
configuration_reader: GroupConfigurationReaderImpl,
) -> FlowyResult<Box<dyn GroupController>> {
let configuration_reader = GroupConfigurationReaderImpl(view_rev_pad.clone());
let configuration_writer = GroupConfigurationWriterImpl {
user_id,
rev_manager,

View File

@ -1,9 +1,10 @@
use crate::entities::{GridLayout, GridLayoutPB, GridSettingPB};
use crate::services::field::RowSingleCellData;
use crate::services::filter::{FilterController, FilterDelegate, FilterType};
use crate::services::group::{GroupConfigurationReader, GroupConfigurationWriter};
use crate::services::row::GridBlockRowRevision;
use crate::services::sort::{SortDelegate, SortType};
use crate::services::view_editor::GridViewEditorDelegate;
use crate::services::view_editor::{get_cells_for_field, GridViewEditorDelegate};
use bytes::Bytes;
use flowy_database::ConnectionPool;
use flowy_error::{FlowyError, FlowyResult};
@ -56,11 +57,14 @@ impl RevisionMergeable for GridViewRevisionMergeable {
}
}
pub(crate) struct GroupConfigurationReaderImpl(pub(crate) Arc<RwLock<GridViewRevisionPad>>);
pub(crate) struct GroupConfigurationReaderImpl {
pub(crate) pad: Arc<RwLock<GridViewRevisionPad>>,
pub(crate) view_editor_delegate: Arc<dyn GridViewEditorDelegate>,
}
impl GroupConfigurationReader for GroupConfigurationReaderImpl {
fn get_configuration(&self) -> Fut<Option<Arc<GroupConfigurationRevision>>> {
let view_pad = self.0.clone();
let view_pad = self.pad.clone();
to_fut(async move {
let mut groups = view_pad.read().await.get_all_groups();
if groups.is_empty() {
@ -71,6 +75,12 @@ impl GroupConfigurationReader for GroupConfigurationReaderImpl {
}
})
}
fn get_configuration_cells(&self, field_id: &str) -> Fut<FlowyResult<Vec<RowSingleCellData>>> {
let field_id = field_id.to_owned();
let view_editor_delegate = self.view_editor_delegate.clone();
to_fut(async move { get_cells_for_field(view_editor_delegate, &field_id).await })
}
}
pub(crate) struct GroupConfigurationWriterImpl {

View File

@ -62,7 +62,11 @@ async fn grid_cell_update() {
async fn text_cell_date_test() {
let test = GridCellTest::new().await;
let text_field = test.get_first_field_rev(FieldType::RichText);
let cells = test.editor.get_cell_data_for_field(&text_field.id).await.unwrap();
let cells = test
.editor
.get_cells_for_field(&test.view_id, &text_field.id)
.await
.unwrap();
for (i, cell) in cells.iter().enumerate() {
let text = cell.get_text_field_cell_data().unwrap();
@ -82,7 +86,11 @@ async fn text_cell_date_test() {
async fn url_cell_date_test() {
let test = GridCellTest::new().await;
let url_field = test.get_first_field_rev(FieldType::URL);
let cells = test.editor.get_cell_data_for_field(&url_field.id).await.unwrap();
let cells = test
.editor
.get_cells_for_field(&test.view_id, &url_field.id)
.await
.unwrap();
for (i, cell) in cells.iter().enumerate() {
let url_cell_data = cell.get_url_field_cell_data().unwrap();