Merge pull request #1456 from AppFlowy-IO/filter_bloc

Add Filter bloc
This commit is contained in:
Nathan.fooo 2022-11-16 08:50:03 +08:00 committed by GitHub
commit 276df8202a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
34 changed files with 799 additions and 124 deletions

View File

@ -39,7 +39,7 @@ class GroupController {
void startListening() { void startListening() {
_listener.start(onGroupChanged: (result) { _listener.start(onGroupChanged: (result) {
result.fold( result.fold(
(GroupChangesetPB changeset) { (GroupRowsNotificationPB changeset) {
for (final deletedRow in changeset.deletedRows) { for (final deletedRow in changeset.deletedRows) {
group.rows.removeWhere((rowPB) => rowPB.id == deletedRow); group.rows.removeWhere((rowPB) => rowPB.id == deletedRow);
delegate.removeRow(group, deletedRow); delegate.removeRow(group, deletedRow);

View File

@ -8,7 +8,7 @@ import 'package:flowy_sdk/protobuf/flowy-grid/group.pb.dart';
import 'package:dartz/dartz.dart'; import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart'; import 'package:flowy_sdk/protobuf/flowy-grid/group_changeset.pb.dart';
typedef UpdateGroupNotifiedValue = Either<GroupChangesetPB, FlowyError>; typedef UpdateGroupNotifiedValue = Either<GroupRowsNotificationPB, FlowyError>;
class GroupListener { class GroupListener {
final GroupPB group; final GroupPB group;
@ -34,7 +34,7 @@ class GroupListener {
case GridNotification.DidUpdateGroup: case GridNotification.DidUpdateGroup:
result.fold( result.fold(
(payload) => _groupNotifier?.value = (payload) => _groupNotifier?.value =
left(GroupChangesetPB.fromBuffer(payload)), left(GroupRowsNotificationPB.fromBuffer(payload)),
(error) => _groupNotifier?.value = right(error), (error) => _groupNotifier?.value = right(error),
); );
break; break;

View File

@ -0,0 +1,206 @@
import 'package:app_flowy/plugins/grid/application/filter/filter_listener.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbenum.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
import 'dart:async';
import 'filter_service.dart';
part 'filter_bloc.freezed.dart';
class GridFilterBloc extends Bloc<GridFilterEvent, GridFilterState> {
final String viewId;
final FilterFFIService _ffiService;
final FilterListener _listener;
GridFilterBloc({required this.viewId})
: _ffiService = FilterFFIService(viewId: viewId),
_listener = FilterListener(viewId: viewId),
super(GridFilterState.initial()) {
on<GridFilterEvent>(
(event, emit) async {
event.when(
initial: () async {
_startListening();
await _loadFilters();
},
deleteFilter: (
String fieldId,
String filterId,
FieldType fieldType,
) {
_ffiService.deleteFilter(
fieldId: fieldId,
filterId: filterId,
fieldType: fieldType,
);
},
didReceiveFilters: (filters) {
emit(state.copyWith(filters: filters));
},
createCheckboxFilter: (
String fieldId,
CheckboxFilterCondition condition,
) {
_ffiService.createCheckboxFilter(
fieldId: fieldId,
condition: condition,
);
},
createNumberFilter: (
String fieldId,
NumberFilterCondition condition,
String content,
) {
_ffiService.createNumberFilter(
fieldId: fieldId,
condition: condition,
content: content,
);
},
createTextFilter: (
String fieldId,
TextFilterCondition condition,
String content,
) {
_ffiService.createTextFilter(
fieldId: fieldId,
condition: condition,
);
},
createDateFilter: (
String fieldId,
DateFilterCondition condition,
int timestamp,
) {
_ffiService.createDateFilter(
fieldId: fieldId,
condition: condition,
timestamp: timestamp,
);
},
createDateFilterInRange: (
String fieldId,
DateFilterCondition condition,
int start,
int end,
) {
_ffiService.createDateFilter(
fieldId: fieldId,
condition: condition,
start: start,
end: end,
);
},
);
},
);
}
void _startListening() {
_listener.start(onFilterChanged: (result) {
result.fold(
(changeset) {
final List<FilterPB> filters = List.from(state.filters);
// Deletes the filters
final deleteFilterIds =
changeset.deleteFilters.map((e) => e.id).toList();
filters.retainWhere(
(element) => !deleteFilterIds.contains(element.id),
);
// Inserts the new fitler if it's not exist
for (final newFilter in changeset.insertFilters) {
final index =
filters.indexWhere((element) => element.id == newFilter.id);
if (index == -1) {
filters.add(newFilter);
}
}
if (!isClosed) {
add(GridFilterEvent.didReceiveFilters(filters));
}
},
(err) => Log.error(err),
);
});
}
Future<void> _loadFilters() async {
final result = await _ffiService.getAllFilters();
result.fold(
(filters) {
if (!isClosed) {
add(GridFilterEvent.didReceiveFilters(filters));
}
},
(err) => Log.error(err),
);
}
@override
Future<void> close() async {
await _listener.stop();
return super.close();
}
}
@freezed
class GridFilterEvent with _$GridFilterEvent {
const factory GridFilterEvent.initial() = _Initial;
const factory GridFilterEvent.didReceiveFilters(List<FilterPB> filters) =
_DidReceiveFilters;
const factory GridFilterEvent.deleteFilter({
required String fieldId,
required String filterId,
required FieldType fieldType,
}) = _DeleteFilter;
const factory GridFilterEvent.createTextFilter({
required String fieldId,
required TextFilterCondition condition,
required String content,
}) = _CreateTextFilter;
const factory GridFilterEvent.createCheckboxFilter({
required String fieldId,
required CheckboxFilterCondition condition,
}) = _CreateCheckboxFilter;
const factory GridFilterEvent.createNumberFilter({
required String fieldId,
required NumberFilterCondition condition,
required String content,
}) = _CreateCheckboxFitler;
const factory GridFilterEvent.createDateFilter({
required String fieldId,
required DateFilterCondition condition,
required int start,
}) = _CreateDateFitler;
const factory GridFilterEvent.createDateFilterInRange({
required String fieldId,
required DateFilterCondition condition,
required int start,
required int end,
}) = _CreateDateFitlerInRange;
}
@freezed
class GridFilterState with _$GridFilterState {
const factory GridFilterState({
required List<FilterPB> filters,
}) = _GridFilterState;
factory GridFilterState.initial() => const GridFilterState(
filters: [],
);
}

View File

@ -0,0 +1,53 @@
import 'dart:typed_data';
import 'package:app_flowy/core/grid_notification.dart';
import 'package:flowy_infra/notifier.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/dart_notification.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/filter_changeset.pb.dart';
import 'package:dartz/dartz.dart';
typedef UpdateFilterNotifiedValue
= Either<FilterChangesetNotificationPB, FlowyError>;
class FilterListener {
final String viewId;
PublishNotifier<UpdateFilterNotifiedValue>? _filterNotifier =
PublishNotifier();
GridNotificationListener? _listener;
FilterListener({required this.viewId});
void start({
required void Function(UpdateFilterNotifiedValue) onFilterChanged,
}) {
_filterNotifier?.addPublishListener(onFilterChanged);
_listener = GridNotificationListener(
objectId: viewId,
handler: _handler,
);
}
void _handler(
GridNotification ty,
Either<Uint8List, FlowyError> result,
) {
switch (ty) {
case GridNotification.DidUpdateFilter:
result.fold(
(payload) => _filterNotifier?.value =
left(FilterChangesetNotificationPB.fromBuffer(payload)),
(error) => _filterNotifier?.value = right(error),
);
break;
default:
break;
}
}
Future<void> stop() async {
await _listener?.stop();
_filterNotifier?.dispose();
_filterNotifier = null;
}
}

View File

@ -0,0 +1,202 @@
import 'package:dartz/dartz.dart';
import 'package:flowy_sdk/dispatch/dispatch.dart';
import 'package:flowy_sdk/log.dart';
import 'package:flowy_sdk/protobuf/flowy-error/errors.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/checkbox_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/date_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/field_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/grid_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/number_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/select_option_filter.pbserver.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/setting_entities.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/util.pb.dart';
import 'package:fixnum/fixnum.dart' as $fixnum;
class FilterFFIService {
final String viewId;
const FilterFFIService({required this.viewId});
Future<Either<List<FilterPB>, FlowyError>> getAllFilters() {
final payload = GridIdPB()..value = viewId;
return GridEventGetAllFilters(payload).send().then((result) {
return result.fold(
(repeated) => left(repeated.items),
(r) => right(r),
);
});
}
Future<Either<Unit, FlowyError>> createTextFilter({
required String fieldId,
required TextFilterCondition condition,
String content = "",
}) {
final filter = TextFilterPB()
..condition = condition
..content = content;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.RichText,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createCheckboxFilter({
required String fieldId,
required CheckboxFilterCondition condition,
}) {
final filter = CheckboxFilterPB()..condition = condition;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.Checkbox,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createNumberFilter({
required String fieldId,
required NumberFilterCondition condition,
String content = "",
}) {
final filter = NumberFilterPB()
..condition = condition
..content = content;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.Number,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createDateFilter({
required String fieldId,
required DateFilterCondition condition,
int? start,
int? end,
int? timestamp,
}) {
var filter = DateFilterPB();
if (timestamp != null) {
filter.timestamp = $fixnum.Int64(timestamp);
} else {
if (start != null && end != null) {
filter.start = $fixnum.Int64(start);
filter.end = $fixnum.Int64(end);
} else {
throw Exception(
"Start and end should not be null if the timestamp is null");
}
}
return createFilter(
fieldId: fieldId,
fieldType: FieldType.DateTime,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createURLFilter({
required String fieldId,
required TextFilterCondition condition,
String content = "",
}) {
final filter = TextFilterPB()
..condition = condition
..content = content;
return createFilter(
fieldId: fieldId,
fieldType: FieldType.URL,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createSingleSelectFilter({
required String fieldId,
required SelectOptionCondition condition,
List<String> optionIds = const [],
}) {
final filter = SelectOptionFilterPB()
..condition = condition
..optionIds.addAll(optionIds);
return createFilter(
fieldId: fieldId,
fieldType: FieldType.SingleSelect,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createMultiSelectFilter({
required String fieldId,
required SelectOptionCondition condition,
List<String> optionIds = const [],
}) {
final filter = SelectOptionFilterPB()
..condition = condition
..optionIds.addAll(optionIds);
return createFilter(
fieldId: fieldId,
fieldType: FieldType.MultiSelect,
data: filter.writeToBuffer(),
);
}
Future<Either<Unit, FlowyError>> createFilter({
required String fieldId,
required FieldType fieldType,
required List<int> data,
}) {
TextFilterCondition.DoesNotContain.value;
final insertFilterPayload = CreateFilterPayloadPB.create()
..fieldId = fieldId
..fieldType = fieldType
..data = data;
final payload = GridSettingChangesetPB.create()
..gridId = viewId
..insertFilter = insertFilterPayload;
return GridEventUpdateGridSetting(payload).send().then((result) {
return result.fold(
(l) => left(l),
(err) {
Log.error(err);
return right(err);
},
);
});
}
Future<Either<Unit, FlowyError>> deleteFilter({
required String fieldId,
required String filterId,
required FieldType fieldType,
}) {
TextFilterCondition.DoesNotContain.value;
final deleteFilterPayload = DeleteFilterPayloadPB.create()
..fieldId = fieldId
..filterId = filterId
..fieldType = fieldType;
final payload = GridSettingChangesetPB.create()
..gridId = viewId
..deleteFilter = deleteFilterPayload;
return GridEventUpdateGridSetting(payload).send().then((result) {
return result.fold(
(l) => left(l),
(err) {
Log.error(err);
return right(err);
},
);
});
}
}

View File

@ -0,0 +1,64 @@
import 'package:app_flowy/plugins/grid/application/filter/filter_bloc.dart';
import 'package:flowy_sdk/protobuf/flowy-grid/text_filter.pb.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:bloc_test/bloc_test.dart';
import 'util.dart';
void main() {
late AppFlowyGridTest gridTest;
setUpAll(() async {
gridTest = await AppFlowyGridTest.ensureInitialized();
});
group('$GridFilterBloc', () {
setUp(() async {
await gridTest.createTestGrid();
});
blocTest<GridFilterBloc, GridFilterState>(
"create a text filter",
build: () => GridFilterBloc(viewId: gridTest.gridView.id)
..add(const GridFilterEvent.initial()),
act: (bloc) async {
final textField = gridTest.textFieldContext();
bloc.add(
GridFilterEvent.createTextFilter(
fieldId: textField.id,
condition: TextFilterCondition.TextIsEmpty,
content: ""),
);
},
wait: const Duration(milliseconds: 300),
verify: (bloc) {
assert(bloc.state.filters.length == 1);
},
);
blocTest<GridFilterBloc, GridFilterState>(
"delete a text filter",
build: () => GridFilterBloc(viewId: gridTest.gridView.id)
..add(const GridFilterEvent.initial()),
act: (bloc) async {
final textField = gridTest.textFieldContext();
bloc.add(
GridFilterEvent.createTextFilter(
fieldId: textField.id,
condition: TextFilterCondition.TextIsEmpty,
content: ""),
);
await gridResponseFuture();
final filter = bloc.state.filters.first;
bloc.add(
GridFilterEvent.deleteFilter(
fieldId: textField.id,
filterId: filter.id,
fieldType: textField.fieldType,
),
);
},
wait: const Duration(milliseconds: 300),
verify: (bloc) {
assert(bloc.state.filters.isEmpty);
},
);
});
}

View File

@ -157,6 +157,12 @@ class AppFlowyGridTest {
return GridFieldCellContext(gridId: gridView.id, field: field); return GridFieldCellContext(gridId: gridView.id, field: field);
} }
GridFieldContext textFieldContext() {
final fieldContext = fieldContexts
.firstWhere((element) => element.fieldType == FieldType.RichText);
return fieldContext;
}
Future<void> createTestGrid() async { Future<void> createTestGrid() async {
final app = await unitTest.createTestApp(); final app = await unitTest.createTestApp();
final builder = GridPluginBuilder(); final builder = GridPluginBuilder();

View File

@ -14,6 +14,7 @@ pub enum GridNotification {
DidUpdateGroupView = 60, DidUpdateGroupView = 60,
DidUpdateGroup = 61, DidUpdateGroup = 61,
DidGroupByNewField = 62, DidGroupByNewField = 62,
DidUpdateFilter = 63,
DidUpdateGridSetting = 70, DidUpdateGridSetting = 70,
} }

View File

@ -40,8 +40,8 @@ impl std::convert::TryFrom<u8> for CheckboxFilterCondition {
} }
} }
impl std::convert::From<Arc<FilterRevision>> for CheckboxFilterPB { impl std::convert::From<&FilterRevision> for CheckboxFilterPB {
fn from(rev: Arc<FilterRevision>) -> Self { fn from(rev: &FilterRevision) -> Self {
CheckboxFilterPB { CheckboxFilterPB {
condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked), condition: CheckboxFilterCondition::try_from(rev.condition).unwrap_or(CheckboxFilterCondition::IsChecked),
} }

View File

@ -81,8 +81,8 @@ impl std::convert::TryFrom<u8> for DateFilterCondition {
} }
} }
} }
impl std::convert::From<Arc<FilterRevision>> for DateFilterPB { impl std::convert::From<&FilterRevision> for DateFilterPB {
fn from(rev: Arc<FilterRevision>) -> Self { fn from(rev: &FilterRevision) -> Self {
let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs); let condition = DateFilterCondition::try_from(rev.condition).unwrap_or(DateFilterCondition::DateIs);
let mut filter = DateFilterPB { let mut filter = DateFilterPB {
condition, condition,

View File

@ -0,0 +1,31 @@
use crate::entities::{FilterPB, InsertedRowPB, RepeatedFilterPB, RowPB};
use flowy_derive::ProtoBuf;
#[derive(Debug, Default, ProtoBuf)]
pub struct FilterChangesetNotificationPB {
#[pb(index = 1)]
pub view_id: String,
#[pb(index = 2)]
pub insert_filters: Vec<FilterPB>,
#[pb(index = 3)]
pub delete_filters: Vec<FilterPB>,
}
impl FilterChangesetNotificationPB {
pub fn from_insert(view_id: &str, filters: Vec<FilterPB>) -> Self {
Self {
view_id: view_id.to_string(),
insert_filters: filters,
delete_filters: Default::default(),
}
}
pub fn from_delete(view_id: &str, filters: Vec<FilterPB>) -> Self {
Self {
view_id: view_id.to_string(),
insert_filters: Default::default(),
delete_filters: filters,
}
}
}

View File

@ -1,5 +1,6 @@
mod checkbox_filter; mod checkbox_filter;
mod date_filter; mod date_filter;
mod filter_changeset;
mod number_filter; mod number_filter;
mod select_option_filter; mod select_option_filter;
mod text_filter; mod text_filter;
@ -7,6 +8,7 @@ mod util;
pub use checkbox_filter::*; pub use checkbox_filter::*;
pub use date_filter::*; pub use date_filter::*;
pub use filter_changeset::*;
pub use number_filter::*; pub use number_filter::*;
pub use select_option_filter::*; pub use select_option_filter::*;
pub use text_filter::*; pub use text_filter::*;

View File

@ -55,8 +55,8 @@ impl std::convert::TryFrom<u8> for NumberFilterCondition {
} }
} }
impl std::convert::From<Arc<FilterRevision>> for NumberFilterPB { impl std::convert::From<&FilterRevision> for NumberFilterPB {
fn from(rev: Arc<FilterRevision>) -> Self { fn from(rev: &FilterRevision) -> Self {
NumberFilterPB { NumberFilterPB {
condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal), condition: NumberFilterCondition::try_from(rev.condition).unwrap_or(NumberFilterCondition::Equal),
content: rev.content.clone(), content: rev.content.clone(),

View File

@ -48,8 +48,8 @@ impl std::convert::TryFrom<u8> for SelectOptionCondition {
} }
} }
impl std::convert::From<Arc<FilterRevision>> for SelectOptionFilterPB { impl std::convert::From<&FilterRevision> for SelectOptionFilterPB {
fn from(rev: Arc<FilterRevision>) -> Self { fn from(rev: &FilterRevision) -> Self {
let ids = SelectOptionIds::from(rev.content.clone()); let ids = SelectOptionIds::from(rev.content.clone());
SelectOptionFilterPB { SelectOptionFilterPB {
condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs), condition: SelectOptionCondition::try_from(rev.condition).unwrap_or(SelectOptionCondition::OptionIs),

View File

@ -55,8 +55,8 @@ impl std::convert::TryFrom<u8> for TextFilterCondition {
} }
} }
impl std::convert::From<Arc<FilterRevision>> for TextFilterPB { impl std::convert::From<&FilterRevision> for TextFilterPB {
fn from(rev: Arc<FilterRevision>) -> Self { fn from(rev: &FilterRevision) -> Self {
TextFilterPB { TextFilterPB {
condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is), condition: TextFilterCondition::try_from(rev.condition).unwrap_or(TextFilterCondition::Is),
content: rev.content.clone(), content: rev.content.clone(),

View File

@ -1,9 +1,10 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::{ use crate::entities::{
CheckboxFilterCondition, DateFilterCondition, FieldType, NumberFilterCondition, SelectOptionCondition, CheckboxFilterPB, DateFilterContent, DateFilterPB, FieldType, NumberFilterPB, SelectOptionFilterPB, TextFilterPB,
TextFilterCondition,
}; };
use crate::services::field::SelectOptionIds;
use crate::services::filter::FilterType; use crate::services::filter::FilterType;
use bytes::Bytes;
use flowy_derive::ProtoBuf; use flowy_derive::ProtoBuf;
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision}; use grid_rev_model::{FieldRevision, FieldTypeRevision, FilterRevision};
@ -14,29 +15,49 @@ use std::sync::Arc;
pub struct FilterPB { pub struct FilterPB {
#[pb(index = 1)] #[pb(index = 1)]
pub id: String, pub id: String,
}
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] #[pb(index = 2)]
pub struct RepeatedGridFilterConfigurationPB { pub ty: FieldType,
#[pb(index = 1)]
pub items: Vec<FilterPB>, #[pb(index = 3)]
pub data: Vec<u8>,
} }
impl std::convert::From<&FilterRevision> for FilterPB { impl std::convert::From<&FilterRevision> for FilterPB {
fn from(rev: &FilterRevision) -> Self { fn from(rev: &FilterRevision) -> Self {
Self { id: rev.id.clone() } let field_type: FieldType = rev.field_type_rev.into();
let bytes: Bytes = match field_type {
FieldType::RichText => TextFilterPB::from(rev).try_into().unwrap(),
FieldType::Number => NumberFilterPB::from(rev).try_into().unwrap(),
FieldType::DateTime => DateFilterPB::from(rev).try_into().unwrap(),
FieldType::SingleSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
FieldType::MultiSelect => SelectOptionFilterPB::from(rev).try_into().unwrap(),
FieldType::Checkbox => CheckboxFilterPB::from(rev).try_into().unwrap(),
FieldType::URL => TextFilterPB::from(rev).try_into().unwrap(),
};
Self {
id: rev.id.clone(),
ty: rev.field_type_rev.into(),
data: bytes.to_vec(),
}
} }
} }
impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedGridFilterConfigurationPB { #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct RepeatedFilterPB {
#[pb(index = 1)]
pub items: Vec<FilterPB>,
}
impl std::convert::From<Vec<Arc<FilterRevision>>> for RepeatedFilterPB {
fn from(revs: Vec<Arc<FilterRevision>>) -> Self { fn from(revs: Vec<Arc<FilterRevision>>) -> Self {
RepeatedGridFilterConfigurationPB { RepeatedFilterPB {
items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(), items: revs.into_iter().map(|rev| rev.as_ref().into()).collect(),
} }
} }
} }
impl std::convert::From<Vec<FilterPB>> for RepeatedGridFilterConfigurationPB { impl std::convert::From<Vec<FilterPB>> for RepeatedFilterPB {
fn from(items: Vec<FilterPB>) -> Self { fn from(items: Vec<FilterPB>) -> Self {
Self { items } Self { items }
} }
@ -89,20 +110,17 @@ pub struct CreateFilterPayloadPB {
pub field_type: FieldType, pub field_type: FieldType,
#[pb(index = 3)] #[pb(index = 3)]
pub condition: u32, pub data: Vec<u8>,
#[pb(index = 4)]
pub content: String,
} }
impl CreateFilterPayloadPB { impl CreateFilterPayloadPB {
#[allow(dead_code)] #[allow(dead_code)]
pub fn new<T: Into<u32>>(field_rev: &FieldRevision, condition: T, content: String) -> Self { pub fn new<T: TryInto<Bytes, Error = ::protobuf::ProtobufError>>(field_rev: &FieldRevision, data: T) -> Self {
let data = data.try_into().unwrap_or_else(|_| Bytes::new());
Self { Self {
field_id: field_rev.id.clone(), field_id: field_rev.id.clone(),
field_type: field_rev.ty.into(), field_type: field_rev.ty.into(),
condition: condition.into(), data: data.to_vec(),
content,
} }
} }
} }
@ -114,22 +132,39 @@ impl TryInto<CreateFilterParams> for CreateFilterPayloadPB {
let field_id = NotEmptyStr::parse(self.field_id) let field_id = NotEmptyStr::parse(self.field_id)
.map_err(|_| ErrorCode::FieldIdIsEmpty)? .map_err(|_| ErrorCode::FieldIdIsEmpty)?
.0; .0;
let condition = self.condition as u8; let condition;
let mut content = "".to_string();
let bytes: &[u8] = self.data.as_ref();
match self.field_type { match self.field_type {
FieldType::RichText | FieldType::URL => { FieldType::RichText | FieldType::URL => {
let _ = TextFilterCondition::try_from(condition)?; let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8;
content = filter.content;
} }
FieldType::Checkbox => { FieldType::Checkbox => {
let _ = CheckboxFilterCondition::try_from(condition)?; let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8;
} }
FieldType::Number => { FieldType::Number => {
let _ = NumberFilterCondition::try_from(condition)?; let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8;
content = filter.content;
} }
FieldType::DateTime => { FieldType::DateTime => {
let _ = DateFilterCondition::try_from(condition)?; let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8;
content = DateFilterContent {
start: filter.start,
end: filter.end,
timestamp: filter.timestamp,
}
.to_string();
} }
FieldType::SingleSelect | FieldType::MultiSelect => { FieldType::SingleSelect | FieldType::MultiSelect => {
let _ = SelectOptionCondition::try_from(condition)?; let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?;
condition = filter.condition as u8;
content = SelectOptionIds::from(filter.option_ids).to_string();
} }
} }
@ -137,7 +172,7 @@ impl TryInto<CreateFilterParams> for CreateFilterPayloadPB {
field_id, field_id,
field_type_rev: self.field_type.into(), field_type_rev: self.field_type.into(),
condition, condition,
content: self.content, content,
}) })
} }
} }

View File

@ -5,7 +5,7 @@ use flowy_error::ErrorCode;
use std::fmt::Formatter; use std::fmt::Formatter;
#[derive(Debug, Default, ProtoBuf)] #[derive(Debug, Default, ProtoBuf)]
pub struct GroupChangesetPB { pub struct GroupRowsNotificationPB {
#[pb(index = 1)] #[pb(index = 1)]
pub group_id: String, pub group_id: String,
@ -22,7 +22,7 @@ pub struct GroupChangesetPB {
pub updated_rows: Vec<RowPB>, pub updated_rows: Vec<RowPB>,
} }
impl std::fmt::Display for GroupChangesetPB { impl std::fmt::Display for GroupRowsNotificationPB {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
for inserted_row in &self.inserted_rows { for inserted_row in &self.inserted_rows {
let _ = f.write_fmt(format_args!( let _ = f.write_fmt(format_args!(
@ -39,7 +39,7 @@ impl std::fmt::Display for GroupChangesetPB {
} }
} }
impl GroupChangesetPB { impl GroupRowsNotificationPB {
pub fn is_empty(&self) -> bool { pub fn is_empty(&self) -> bool {
self.group_name.is_none() self.group_name.is_none()
&& self.inserted_rows.is_empty() && self.inserted_rows.is_empty()

View File

@ -1,8 +1,7 @@
use crate::entities::parser::NotEmptyStr; use crate::entities::parser::NotEmptyStr;
use crate::entities::{ use crate::entities::{
CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams, CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, DeleteFilterPayloadPB, DeleteGroupParams,
DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedGridFilterConfigurationPB, DeleteGroupPayloadPB, InsertGroupParams, InsertGroupPayloadPB, RepeatedFilterPB, RepeatedGridGroupConfigurationPB,
RepeatedGridGroupConfigurationPB,
}; };
use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum};
use flowy_error::ErrorCode; use flowy_error::ErrorCode;
@ -21,7 +20,7 @@ pub struct GridSettingPB {
pub layout_type: GridLayout, pub layout_type: GridLayout,
#[pb(index = 3)] #[pb(index = 3)]
pub filter_configurations: RepeatedGridFilterConfigurationPB, pub filter_configurations: RepeatedFilterPB,
#[pb(index = 4)] #[pb(index = 4)]
pub group_configurations: RepeatedGridGroupConfigurationPB, pub group_configurations: RepeatedGridGroupConfigurationPB,

View File

@ -61,6 +61,19 @@ pub(crate) async fn update_grid_setting_handler(
Ok(()) Ok(())
} }
#[tracing::instrument(level = "trace", skip(data, manager), err)]
pub(crate) async fn get_all_filters_handler(
data: Data<GridIdPB>,
manager: AppData<Arc<GridManager>>,
) -> DataResult<RepeatedFilterPB, FlowyError> {
let grid_id: GridIdPB = data.into_inner();
let editor = manager.open_grid(grid_id).await?;
let filters = RepeatedFilterPB {
items: editor.get_all_filters().await?,
};
data_result(filters)
}
#[tracing::instrument(level = "debug", skip(data, manager), err)] #[tracing::instrument(level = "debug", skip(data, manager), err)]
pub(crate) async fn get_grid_blocks_handler( pub(crate) async fn get_grid_blocks_handler(
data: Data<QueryBlocksPayloadPB>, data: Data<QueryBlocksPayloadPB>,

View File

@ -12,6 +12,7 @@ pub fn create(grid_manager: Arc<GridManager>) -> Module {
.event(GridEvent::GetGridBlocks, get_grid_blocks_handler) .event(GridEvent::GetGridBlocks, get_grid_blocks_handler)
.event(GridEvent::GetGridSetting, get_grid_setting_handler) .event(GridEvent::GetGridSetting, get_grid_setting_handler)
.event(GridEvent::UpdateGridSetting, update_grid_setting_handler) .event(GridEvent::UpdateGridSetting, update_grid_setting_handler)
.event(GridEvent::GetAllFilters, get_all_filters_handler)
// Field // Field
.event(GridEvent::GetFields, get_fields_handler) .event(GridEvent::GetFields, get_fields_handler)
.event(GridEvent::UpdateField, update_field_handler) .event(GridEvent::UpdateField, update_field_handler)
@ -78,6 +79,9 @@ pub enum GridEvent {
#[event(input = "GridSettingChangesetPB")] #[event(input = "GridSettingChangesetPB")]
UpdateGridSetting = 3, UpdateGridSetting = 3,
#[event(input = "GridIdPB", output = "RepeatedFilterPB")]
GetAllFilters = 4,
/// [GetFields] event is used to get the grid's settings. /// [GetFields] event is used to get the grid's settings.
/// ///
/// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB] /// The event handler accepts a [GetFieldPayloadPB] and returns a [RepeatedFieldPB]

View File

@ -181,37 +181,37 @@ impl FilterController {
let _ = self let _ = self
.filter_map .filter_map
.text_filter .text_filter
.insert(filter_type, TextFilterPB::from(filter_rev)); .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
} }
FieldType::Number => { FieldType::Number => {
let _ = self let _ = self
.filter_map .filter_map
.number_filter .number_filter
.insert(filter_type, NumberFilterPB::from(filter_rev)); .insert(filter_type, NumberFilterPB::from(filter_rev.as_ref()));
} }
FieldType::DateTime => { FieldType::DateTime => {
let _ = self let _ = self
.filter_map .filter_map
.date_filter .date_filter
.insert(filter_type, DateFilterPB::from(filter_rev)); .insert(filter_type, DateFilterPB::from(filter_rev.as_ref()));
} }
FieldType::SingleSelect | FieldType::MultiSelect => { FieldType::SingleSelect | FieldType::MultiSelect => {
let _ = self let _ = self
.filter_map .filter_map
.select_option_filter .select_option_filter
.insert(filter_type, SelectOptionFilterPB::from(filter_rev)); .insert(filter_type, SelectOptionFilterPB::from(filter_rev.as_ref()));
} }
FieldType::Checkbox => { FieldType::Checkbox => {
let _ = self let _ = self
.filter_map .filter_map
.checkbox_filter .checkbox_filter
.insert(filter_type, CheckboxFilterPB::from(filter_rev)); .insert(filter_type, CheckboxFilterPB::from(filter_rev.as_ref()));
} }
FieldType::URL => { FieldType::URL => {
let _ = self let _ = self
.filter_map .filter_map
.url_filter .url_filter
.insert(filter_type, TextFilterPB::from(filter_rev)); .insert(filter_type, TextFilterPB::from(filter_rev.as_ref()));
} }
} }
} }

View File

@ -132,8 +132,8 @@ impl GridViewRevisionEditor {
index, index,
is_new: true, is_new: true,
}; };
let changeset = GroupChangesetPB::insert(group_id.clone(), vec![inserted_row]); let changeset = GroupRowsNotificationPB::insert(group_id.clone(), vec![inserted_row]);
self.notify_did_update_group(changeset).await; self.notify_did_update_group_rows(changeset).await;
} }
} }
} }
@ -150,7 +150,7 @@ impl GridViewRevisionEditor {
tracing::trace!("Delete row in view changeset: {:?}", changesets); tracing::trace!("Delete row in view changeset: {:?}", changesets);
if let Some(changesets) = changesets { if let Some(changesets) = changesets {
for changeset in changesets { for changeset in changesets {
self.notify_did_update_group(changeset).await; self.notify_did_update_group_rows(changeset).await;
} }
} }
} }
@ -164,7 +164,7 @@ impl GridViewRevisionEditor {
if let Some(changesets) = changesets { if let Some(changesets) = changesets {
for changeset in changesets { for changeset in changesets {
self.notify_did_update_group(changeset).await; self.notify_did_update_group_rows(changeset).await;
} }
} }
} }
@ -175,7 +175,7 @@ impl GridViewRevisionEditor {
row_changeset: &mut RowChangeset, row_changeset: &mut RowChangeset,
to_group_id: &str, to_group_id: &str,
to_row_id: Option<String>, to_row_id: Option<String>,
) -> Vec<GroupChangesetPB> { ) -> Vec<GroupRowsNotificationPB> {
let changesets = self let changesets = self
.mut_group_controller(|group_controller, field_rev| { .mut_group_controller(|group_controller, field_rev| {
let move_row_context = MoveGroupRowContext { let move_row_context = MoveGroupRowContext {
@ -252,9 +252,12 @@ impl GridViewRevisionEditor {
self.pad.read().await.get_all_filters(&field_revs) self.pad.read().await.get_all_filters(&field_revs)
} }
pub(crate) async fn get_view_filters(&self, filter_id: &FilterType) -> Vec<Arc<FilterRevision>> { pub(crate) async fn get_view_filters(&self, filter_type: &FilterType) -> Vec<Arc<FilterRevision>> {
let field_type_rev: FieldTypeRevision = filter_id.field_type.clone().into(); let field_type_rev: FieldTypeRevision = filter_type.field_type.clone().into();
self.pad.read().await.get_filters(&filter_id.field_id, &field_type_rev) self.pad
.read()
.await
.get_filters(&filter_type.field_id, &field_type_rev)
} }
/// Initialize new group when grouping by a new field /// Initialize new group when grouping by a new field
@ -290,14 +293,16 @@ impl GridViewRevisionEditor {
pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> { pub(crate) async fn insert_view_filter(&self, params: CreateFilterParams) -> FlowyResult<()> {
let filter_type = FilterType::from(&params); let filter_type = FilterType::from(&params);
let filter_rev = FilterRevision {
id: gen_grid_filter_id(),
field_id: params.field_id.clone(),
field_type_rev: params.field_type_rev,
condition: params.condition,
content: params.content,
};
let filter_pb = FilterPB::from(&filter_rev);
let _ = self let _ = self
.modify(|pad| { .modify(|pad| {
let filter_rev = FilterRevision {
id: gen_grid_filter_id(),
field_id: params.field_id.clone(),
condition: params.condition,
content: params.content,
};
let changeset = pad.insert_filter(&params.field_id, &params.field_type_rev, filter_rev)?; let changeset = pad.insert_filter(&params.field_id, &params.field_type_rev, filter_rev)?;
Ok(changeset) Ok(changeset)
}) })
@ -309,12 +314,20 @@ impl GridViewRevisionEditor {
.apply_changeset(FilterChangeset::from_insert(filter_type)) .apply_changeset(FilterChangeset::from_insert(filter_type))
.await; .await;
let changeset = FilterChangesetNotificationPB::from_insert(&self.view_id, vec![filter_pb]);
self.notify_did_update_filter(changeset).await;
Ok(()) Ok(())
} }
pub(crate) async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> { pub(crate) async fn delete_view_filter(&self, params: DeleteFilterParams) -> FlowyResult<()> {
let filter_type = params.filter_type; let filter_type = params.filter_type;
let field_type_rev = filter_type.field_type_rev(); let field_type_rev = filter_type.field_type_rev();
let filters = self
.get_view_filters(&filter_type)
.await
.into_iter()
.map(|filter| FilterPB::from(filter.as_ref()))
.collect();
let _ = self let _ = self
.modify(|pad| { .modify(|pad| {
let changeset = pad.delete_filter(&params.filter_id, &filter_type.field_id, &field_type_rev)?; let changeset = pad.delete_filter(&params.filter_id, &filter_type.field_id, &field_type_rev)?;
@ -327,6 +340,9 @@ impl GridViewRevisionEditor {
.await .await
.apply_changeset(FilterChangeset::from_delete(filter_type)) .apply_changeset(FilterChangeset::from_delete(filter_type))
.await; .await;
let changeset = FilterChangesetNotificationPB::from_delete(&self.view_id, filters);
self.notify_did_update_filter(changeset).await;
Ok(()) Ok(())
} }
@ -394,8 +410,14 @@ impl GridViewRevisionEditor {
.send(); .send();
} }
pub async fn notify_did_update_group(&self, changeset: GroupChangesetPB) { pub async fn notify_did_update_group_rows(&self, payload: GroupRowsNotificationPB) {
send_dart_notification(&changeset.group_id, GridNotification::DidUpdateGroup) send_dart_notification(&payload.group_id, GridNotification::DidUpdateGroup)
.payload(payload)
.send();
}
pub async fn notify_did_update_filter(&self, changeset: FilterChangesetNotificationPB) {
send_dart_notification(&changeset.view_id, GridNotification::DidUpdateFilter)
.payload(changeset) .payload(changeset)
.send(); .send();
} }

View File

@ -168,7 +168,7 @@ impl GridViewManager {
} }
for group_changeset in group_changesets { for group_changeset in group_changesets {
view_editor.notify_did_update_group(group_changeset).await; view_editor.notify_did_update_group_rows(group_changeset).await;
} }
Ok(()) Ok(())

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB}; use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB};
use crate::services::cell::CellDataIsEmpty; use crate::services::cell::CellDataIsEmpty;
use crate::services::group::controller::MoveGroupRowContext; use crate::services::group::controller::MoveGroupRowContext;
use crate::services::group::Group; use crate::services::group::Group;
@ -31,13 +31,17 @@ pub trait GroupControllerCustomActions: Send + Sync {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
cell_data: &Self::CellDataType, cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB>; ) -> Vec<GroupRowsNotificationPB>;
/// Deletes the row from the group /// Deletes the row from the group
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB>; fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB>;
/// Move row from one group to another /// Move row from one group to another
fn move_row(&mut self, cell_data: &Self::CellDataType, context: MoveGroupRowContext) -> Vec<GroupChangesetPB>; fn move_row(
&mut self,
cell_data: &Self::CellDataType,
context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB>;
} }
/// Defines the shared actions any group controller can perform. /// Defines the shared actions any group controller can perform.
@ -62,17 +66,17 @@ pub trait GroupControllerSharedActions: Send + Sync {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>>; ) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
/// Remove the row from the group if the row gets deleted /// Remove the row from the group if the row gets deleted
fn did_delete_delete_row( fn did_delete_delete_row(
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>>; ) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
/// Move the row from one group to another group /// Move the row from one group to another group
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>>; fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>>;
/// Update the group if the corresponding field is changed /// Update the group if the corresponding field is changed
fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>; fn did_update_group_field(&mut self, field_rev: &FieldRevision) -> FlowyResult<Option<GroupViewChangesetPB>>;

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, InsertedRowPB, RowPB}; use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, InsertedRowPB, RowPB};
use crate::services::cell::{decode_any_cell_data, CellBytesParser, CellDataIsEmpty}; use crate::services::cell::{decode_any_cell_data, CellBytesParser, CellDataIsEmpty};
use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions}; use crate::services::group::action::{GroupControllerCustomActions, GroupControllerSharedActions};
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
@ -89,8 +89,8 @@ where
fn update_default_group( fn update_default_group(
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
other_group_changesets: &[GroupChangesetPB], other_group_changesets: &[GroupRowsNotificationPB],
) -> Option<GroupChangesetPB> { ) -> Option<GroupRowsNotificationPB> {
let default_group = self.group_ctx.get_mut_no_status_group()?; let default_group = self.group_ctx.get_mut_no_status_group()?;
// [other_group_inserted_row] contains all the inserted rows except the default group. // [other_group_inserted_row] contains all the inserted rows except the default group.
@ -113,7 +113,7 @@ where
}) })
.collect::<Vec<String>>(); .collect::<Vec<String>>();
let mut changeset = GroupChangesetPB::new(default_group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(default_group.id.clone());
if !default_group_inserted_row.is_empty() { if !default_group_inserted_row.is_empty() {
changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into())); changeset.inserted_rows.push(InsertedRowPB::new(row_rev.into()));
default_group.add_row(row_rev.into()); default_group.add_row(row_rev.into());
@ -222,7 +222,7 @@ where
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
let cell_data = cell_bytes.parser::<P>()?; let cell_data = cell_bytes.parser::<P>()?;
@ -244,7 +244,7 @@ where
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
field_rev: &FieldRevision, field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
// if the cell_rev is none, then the row must in the default group. // if the cell_rev is none, then the row must in the default group.
if let Some(cell_rev) = row_rev.cells.get(&self.field_id) { if let Some(cell_rev) = row_rev.cells.get(&self.field_id) {
let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1; let cell_bytes = decode_any_cell_data(cell_rev.data.clone(), field_rev).1;
@ -264,7 +264,7 @@ where
if !no_status_group.contains_row(&row_rev.id) { if !no_status_group.contains_row(&row_rev.id) {
tracing::error!("The row: {} should be in the no status group", row_rev.id); tracing::error!("The row: {} should be in the no status group", row_rev.id);
} }
Ok(vec![GroupChangesetPB::delete( Ok(vec![GroupRowsNotificationPB::delete(
no_status_group.id.clone(), no_status_group.id.clone(),
vec![row_rev.id.clone()], vec![row_rev.id.clone()],
)]) )])
@ -273,7 +273,7 @@ where
} }
#[tracing::instrument(level = "trace", skip_all, err)] #[tracing::instrument(level = "trace", skip_all, err)]
fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> { fn move_group_row(&mut self, context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
let cell_rev = match context.row_rev.cells.get(&self.field_id) { let cell_rev = match context.row_rev.cells.get(&self.field_id) {
Some(cell_rev) => Some(cell_rev.clone()), Some(cell_rev) => Some(cell_rev.clone()),
None => self.default_cell_rev(), None => self.default_cell_rev(),

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupChangesetPB, InsertedRowPB, RowPB}; use crate::entities::{GroupRowsNotificationPB, InsertedRowPB, RowPB};
use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK}; use crate::services::field::{CheckboxCellData, CheckboxCellDataParser, CheckboxTypeOptionPB, CHECK, UNCHECK};
use crate::services::group::action::GroupControllerCustomActions; use crate::services::group::action::GroupControllerCustomActions;
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
@ -37,10 +37,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
cell_data: &Self::CellDataType, cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
let is_not_contained = !group.contains_row(&row_rev.id); let is_not_contained = !group.contains_row(&row_rev.id);
if group.id == CHECK { if group.id == CHECK {
if cell_data.is_uncheck() { if cell_data.is_uncheck() {
@ -79,10 +79,10 @@ impl GroupControllerCustomActions for CheckboxGroupController {
changesets changesets
} }
fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn delete_row(&mut self, row_rev: &RowRevision, _cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
if group.contains_row(&row_rev.id) { if group.contains_row(&row_rev.id) {
changeset.deleted_rows.push(row_rev.id.clone()); changeset.deleted_rows.push(row_rev.id.clone());
group.remove_row(&row_rev.id); group.remove_row(&row_rev.id);
@ -95,7 +95,11 @@ impl GroupControllerCustomActions for CheckboxGroupController {
changesets changesets
} }
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> { fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.group_ctx.iter_mut_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
if let Some(changeset) = move_group_row(group, &mut context) { if let Some(changeset) = move_group_row(group, &mut context) {

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupChangesetPB, GroupViewChangesetPB, RowPB}; use crate::entities::{GroupRowsNotificationPB, GroupViewChangesetPB, RowPB};
use crate::services::group::action::GroupControllerSharedActions; use crate::services::group::action::GroupControllerSharedActions;
use crate::services::group::{Group, GroupController, MoveGroupRowContext}; use crate::services::group::{Group, GroupController, MoveGroupRowContext};
use flowy_error::FlowyResult; use flowy_error::FlowyResult;
@ -59,7 +59,7 @@ impl GroupControllerSharedActions for DefaultGroupController {
&mut self, &mut self,
_row_rev: &RowRevision, _row_rev: &RowRevision,
_field_rev: &FieldRevision, _field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
Ok(vec![]) Ok(vec![])
} }
@ -67,11 +67,11 @@ impl GroupControllerSharedActions for DefaultGroupController {
&mut self, &mut self,
_row_rev: &RowRevision, _row_rev: &RowRevision,
_field_rev: &FieldRevision, _field_rev: &FieldRevision,
) -> FlowyResult<Vec<GroupChangesetPB>> { ) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
Ok(vec![]) Ok(vec![])
} }
fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupChangesetPB>> { fn move_group_row(&mut self, _context: MoveGroupRowContext) -> FlowyResult<Vec<GroupRowsNotificationPB>> {
todo!() todo!()
} }

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupChangesetPB, RowPB}; use crate::entities::{GroupRowsNotificationPB, RowPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser}; use crate::services::field::{MultiSelectTypeOptionPB, SelectOptionCellDataPB, SelectOptionCellDataParser};
use crate::services::group::action::GroupControllerCustomActions; use crate::services::group::action::GroupControllerCustomActions;
@ -30,7 +30,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
cell_data: &Self::CellDataType, cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
changesets changesets
} }
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
@ -50,7 +50,11 @@ impl GroupControllerCustomActions for MultiSelectGroupController {
changesets changesets
} }
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> { fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.group_ctx.iter_mut_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
if let Some(changeset) = move_group_row(group, &mut context) { if let Some(changeset) = move_group_row(group, &mut context) {

View File

@ -1,4 +1,4 @@
use crate::entities::{GroupChangesetPB, RowPB}; use crate::entities::{GroupRowsNotificationPB, RowPB};
use crate::services::cell::insert_select_option_cell; use crate::services::cell::insert_select_option_cell;
use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB}; use crate::services::field::{SelectOptionCellDataPB, SelectOptionCellDataParser, SingleSelectTypeOptionPB};
use crate::services::group::action::GroupControllerCustomActions; use crate::services::group::action::GroupControllerCustomActions;
@ -30,7 +30,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
&mut self, &mut self,
row_rev: &RowRevision, row_rev: &RowRevision,
cell_data: &Self::CellDataType, cell_data: &Self::CellDataType,
) -> Vec<GroupChangesetPB> { ) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = add_or_remove_select_option_row(group, cell_data, row_rev) {
@ -40,7 +40,7 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
changesets changesets
} }
fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupChangesetPB> { fn delete_row(&mut self, row_rev: &RowRevision, cell_data: &Self::CellDataType) -> Vec<GroupRowsNotificationPB> {
let mut changesets = vec![]; let mut changesets = vec![];
self.group_ctx.iter_mut_status_groups(|group| { self.group_ctx.iter_mut_status_groups(|group| {
if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) { if let Some(changeset) = remove_select_option_row(group, cell_data, row_rev) {
@ -50,7 +50,11 @@ impl GroupControllerCustomActions for SingleSelectGroupController {
changesets changesets
} }
fn move_row(&mut self, _cell_data: &Self::CellDataType, mut context: MoveGroupRowContext) -> Vec<GroupChangesetPB> { fn move_row(
&mut self,
_cell_data: &Self::CellDataType,
mut context: MoveGroupRowContext,
) -> Vec<GroupRowsNotificationPB> {
let mut group_changeset = vec![]; let mut group_changeset = vec![];
self.group_ctx.iter_mut_groups(|group| { self.group_ctx.iter_mut_groups(|group| {
if let Some(changeset) = move_group_row(group, &mut context) { if let Some(changeset) = move_group_row(group, &mut context) {

View File

@ -1,4 +1,4 @@
use crate::entities::{FieldType, GroupChangesetPB, InsertedRowPB, RowPB}; use crate::entities::{FieldType, GroupRowsNotificationPB, InsertedRowPB, RowPB};
use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell}; use crate::services::cell::{insert_checkbox_cell, insert_select_option_cell};
use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK}; use crate::services::field::{SelectOptionCellDataPB, SelectOptionPB, CHECK};
use crate::services::group::configuration::GroupContext; use crate::services::group::configuration::GroupContext;
@ -12,8 +12,8 @@ pub fn add_or_remove_select_option_row(
group: &mut Group, group: &mut Group,
cell_data: &SelectOptionCellDataPB, cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision, row_rev: &RowRevision,
) -> Option<GroupChangesetPB> { ) -> Option<GroupRowsNotificationPB> {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
if cell_data.select_options.is_empty() { if cell_data.select_options.is_empty() {
if group.contains_row(&row_rev.id) { if group.contains_row(&row_rev.id) {
changeset.deleted_rows.push(row_rev.id.clone()); changeset.deleted_rows.push(row_rev.id.clone());
@ -45,8 +45,8 @@ pub fn remove_select_option_row(
group: &mut Group, group: &mut Group,
cell_data: &SelectOptionCellDataPB, cell_data: &SelectOptionCellDataPB,
row_rev: &RowRevision, row_rev: &RowRevision,
) -> Option<GroupChangesetPB> { ) -> Option<GroupRowsNotificationPB> {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
cell_data.select_options.iter().for_each(|option| { cell_data.select_options.iter().for_each(|option| {
if option.id == group.id && group.contains_row(&row_rev.id) { if option.id == group.id && group.contains_row(&row_rev.id) {
changeset.deleted_rows.push(row_rev.id.clone()); changeset.deleted_rows.push(row_rev.id.clone());
@ -61,8 +61,8 @@ pub fn remove_select_option_row(
} }
} }
pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option<GroupChangesetPB> { pub fn move_group_row(group: &mut Group, context: &mut MoveGroupRowContext) -> Option<GroupRowsNotificationPB> {
let mut changeset = GroupChangesetPB::new(group.id.clone()); let mut changeset = GroupRowsNotificationPB::new(group.id.clone());
let MoveGroupRowContext { let MoveGroupRowContext {
row_rev, row_rev,
row_changeset, row_changeset,

View File

@ -3,8 +3,9 @@
#![allow(dead_code)] #![allow(dead_code)]
#![allow(unused_imports)] #![allow(unused_imports)]
use bytes::Bytes;
use futures::TryFutureExt; use futures::TryFutureExt;
use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition}; use flowy_grid::entities::{CreateFilterParams, CreateFilterPayloadPB, DeleteFilterParams, GridLayout, GridSettingChangesetParams, GridSettingPB, RowPB, TextFilterCondition, FieldType, NumberFilterCondition, CheckboxFilterCondition, DateFilterCondition, DateFilterContent, SelectOptionCondition, TextFilterPB, NumberFilterPB, CheckboxFilterPB, DateFilterPB, SelectOptionFilterPB};
use flowy_grid::services::field::SelectOptionIds; use flowy_grid::services::field::SelectOptionIds;
use flowy_grid::services::setting::GridSettingChangesetBuilder; use flowy_grid::services::setting::GridSettingChangesetBuilder;
use grid_rev_model::{FieldRevision, FieldTypeRevision}; use grid_rev_model::{FieldRevision, FieldTypeRevision};
@ -85,49 +86,60 @@ impl GridFilterTest {
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::CreateTextFilter { condition, content} => { FilterScript::CreateTextFilter { condition, content} => {
let field_rev = self.get_field_rev(FieldType::RichText); let field_rev = self.get_field_rev(FieldType::RichText);
let text_filter= TextFilterPB {
condition,
content
};
let payload = let payload =
CreateFilterPayloadPB::new(field_rev, condition, content); CreateFilterPayloadPB::new(field_rev, text_filter);
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::CreateNumberFilter {condition, content} => { FilterScript::CreateNumberFilter {condition, content} => {
let field_rev = self.get_field_rev(FieldType::Number); let field_rev = self.get_field_rev(FieldType::Number);
let number_filter = NumberFilterPB {
condition,
content
};
let payload = let payload =
CreateFilterPayloadPB::new(field_rev, condition, content); CreateFilterPayloadPB::new(field_rev, number_filter);
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::CreateCheckboxFilter {condition} => { FilterScript::CreateCheckboxFilter {condition} => {
let field_rev = self.get_field_rev(FieldType::Checkbox); let field_rev = self.get_field_rev(FieldType::Checkbox);
let checkbox_filter = CheckboxFilterPB {
condition
};
let payload = let payload =
CreateFilterPayloadPB::new(field_rev, condition, "".to_string()); CreateFilterPayloadPB::new(field_rev, checkbox_filter);
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::CreateDateFilter { condition, start, end, timestamp} => { FilterScript::CreateDateFilter { condition, start, end, timestamp} => {
let field_rev = self.get_field_rev(FieldType::DateTime); let field_rev = self.get_field_rev(FieldType::DateTime);
let content = DateFilterContent { let date_filter = DateFilterPB {
condition,
start, start,
end, end,
timestamp, timestamp
}.to_string(); };
let payload =
CreateFilterPayloadPB::new(field_rev, condition, content);
let payload =
CreateFilterPayloadPB::new(field_rev, date_filter);
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::CreateMultiSelectFilter { condition, option_ids} => { FilterScript::CreateMultiSelectFilter { condition, option_ids} => {
let field_rev = self.get_field_rev(FieldType::MultiSelect); let field_rev = self.get_field_rev(FieldType::MultiSelect);
let content = let filter = SelectOptionFilterPB { condition, option_ids };
SelectOptionIds::from(option_ids).to_string();
let payload = let payload =
CreateFilterPayloadPB::new(field_rev, condition, content); CreateFilterPayloadPB::new(field_rev, filter);
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::CreateSingleSelectFilter { condition, option_ids} => { FilterScript::CreateSingleSelectFilter { condition, option_ids} => {
let field_rev = self.get_field_rev(FieldType::SingleSelect); let field_rev = self.get_field_rev(FieldType::SingleSelect);
let content = let filter = SelectOptionFilterPB { condition, option_ids };
SelectOptionIds::from(option_ids).to_string();
let payload = let payload =
CreateFilterPayloadPB::new(field_rev, condition, content); CreateFilterPayloadPB::new(field_rev, filter);
self.insert_filter(payload).await; self.insert_filter(payload).await;
} }
FilterScript::AssertFilterCount { count } => { FilterScript::AssertFilterCount { count } => {

View File

@ -1,6 +1,6 @@
use crate::grid::filter_test::script::FilterScript::*; use crate::grid::filter_test::script::FilterScript::*;
use crate::grid::filter_test::script::*; use crate::grid::filter_test::script::*;
use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition}; use flowy_grid::entities::{CreateFilterPayloadPB, FieldType, TextFilterCondition, TextFilterPB};
use flowy_grid::services::filter::FilterType; use flowy_grid::services::filter::FilterType;
#[tokio::test] #[tokio::test]
@ -73,7 +73,11 @@ async fn grid_filter_ends_with_text_test() {
async fn grid_filter_delete_test() { async fn grid_filter_delete_test() {
let mut test = GridFilterTest::new().await; let mut test = GridFilterTest::new().await;
let field_rev = test.get_field_rev(FieldType::RichText).clone(); let field_rev = test.get_field_rev(FieldType::RichText).clone();
let payload = CreateFilterPayloadPB::new(&field_rev, TextFilterCondition::TextIsEmpty, "".to_string()); let text_filter = TextFilterPB {
condition: TextFilterCondition::TextIsEmpty,
content: "".to_string(),
};
let payload = CreateFilterPayloadPB::new(&field_rev, text_filter);
let scripts = vec![ let scripts = vec![
InsertFilter { payload }, InsertFilter { payload },
AssertFilterCount { count: 1 }, AssertFilterCount { count: 1 },

View File

@ -129,6 +129,9 @@ pub enum ErrorCode {
#[display(fmt = "Serde")] #[display(fmt = "Serde")]
Serde = 1001, Serde = 1001,
#[display(fmt = "Protobuf serde")]
ProtobufSerde = 1002,
#[display(fmt = "Out of bounds")] #[display(fmt = "Out of bounds")]
OutOfBounds = 10001, OutOfBounds = 10001,
} }

View File

@ -1,9 +1,11 @@
use crate::FieldTypeRevision;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)] #[derive(Debug, Clone, Serialize, Deserialize, Default, PartialEq, Eq, Hash)]
pub struct FilterRevision { pub struct FilterRevision {
pub id: String, pub id: String,
pub field_id: String, pub field_id: String,
pub field_type_rev: FieldTypeRevision,
pub condition: u8, pub condition: u8,
#[serde(default)] #[serde(default)]
pub content: String, pub content: String,