diff --git a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart index 5beb2bba88..db1a56071e 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/application/field/field_controller.dart @@ -162,75 +162,6 @@ class FieldController { /// Listen for filter changes in the backend. void _listenOnFilterChanges() { - void deleteFilterFromChangeset( - List filters, - FilterChangesetNotificationPB changeset, - ) { - final deleteFilterIds = changeset.deleteFilters.map((e) => e.id).toList(); - if (deleteFilterIds.isNotEmpty) { - filters.retainWhere( - (element) => !deleteFilterIds.contains(element.filter.id), - ); - } - } - - void insertFilterFromChangeset( - List filters, - FilterChangesetNotificationPB changeset, - ) { - for (final newFilter in changeset.insertFilters) { - final filterIndex = - filters.indexWhere((element) => element.filter.id == newFilter.id); - if (filterIndex == -1) { - final fieldInfo = _findFieldInfo( - fieldInfos: fieldInfos, - fieldId: newFilter.fieldId, - fieldType: newFilter.fieldType, - ); - if (fieldInfo != null) { - filters.add(FilterInfo(viewId, newFilter, fieldInfo)); - } - } - } - } - - void updateFilterFromChangeset( - List filters, - FilterChangesetNotificationPB changeset, - ) { - for (final updatedFilter in changeset.updateFilters) { - final filterIndex = filters.indexWhere( - (element) => element.filter.id == updatedFilter.filterId, - ); - // Remove the old filter - if (filterIndex != -1) { - filters.removeAt(filterIndex); - } - - // Insert the filter if there is a filter and its field info is - // not null - if (updatedFilter.hasFilter()) { - final fieldInfo = _findFieldInfo( - fieldInfos: fieldInfos, - fieldId: updatedFilter.filter.fieldId, - fieldType: updatedFilter.filter.fieldType, - ); - - if (fieldInfo != null) { - // Insert the filter with the position: filterIndex, otherwise, - // append it to the end of the list. - final filterInfo = - FilterInfo(viewId, updatedFilter.filter, fieldInfo); - if (filterIndex != -1) { - filters.insert(filterIndex, filterInfo); - } else { - filters.add(filterInfo); - } - } - } - } - } - _filtersListener.start( onFilterChanged: (result) { if (_isDisposed) { @@ -239,15 +170,19 @@ class FieldController { result.fold( (FilterChangesetNotificationPB changeset) { - final List filters = filterInfos; - // delete removed filters - deleteFilterFromChangeset(filters, changeset); + final List filters = []; + for (final filter in changeset.filters.items) { + final fieldInfo = _findFieldInfo( + fieldInfos: fieldInfos, + fieldId: filter.data.fieldId, + fieldType: filter.data.fieldType, + ); - // insert new filters - insertFilterFromChangeset(filters, changeset); - - // edit modified filters - updateFilterFromChangeset(filters, changeset); + if (fieldInfo != null) { + final filterInfo = FilterInfo(viewId, filter, fieldInfo); + filters.add(filterInfo); + } + } _filterNotifier?.filters = filters; _updateFieldInfos(); @@ -665,8 +600,8 @@ class FieldController { FilterInfo? getFilterInfo(FilterPB filterPB) { final fieldInfo = _findFieldInfo( fieldInfos: fieldInfos, - fieldId: filterPB.fieldId, - fieldType: filterPB.fieldType, + fieldId: filterPB.data.fieldId, + fieldType: filterPB.data.fieldType, ); return fieldInfo != null ? FilterInfo(viewId, filterPB, fieldInfo) : null; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/filter_listener.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/filter_listener.dart index f1b0a82723..a9295e38dd 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/filter_listener.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/filter_listener.dart @@ -61,19 +61,11 @@ class FilterListener { final String viewId; final String filterId; - PublishNotifier? _onDeleteNotifier = PublishNotifier(); PublishNotifier? _onUpdateNotifier = PublishNotifier(); DatabaseNotificationListener? _listener; - void start({ - void Function()? onDeleted, - void Function(FilterPB)? onUpdated, - }) { - _onDeleteNotifier?.addPublishListener((_) { - onDeleted?.call(); - }); - + void start({void Function(FilterPB)? onUpdated}) { _onUpdateNotifier?.addPublishListener((filter) { onUpdated?.call(filter); }); @@ -85,20 +77,12 @@ class FilterListener { } void handleChangeset(FilterChangesetNotificationPB changeset) { - // check the delete filter - final deletedIndex = changeset.deleteFilters.indexWhere( - (element) => element.id == filterId, - ); - if (deletedIndex != -1) { - _onDeleteNotifier?.value = changeset.deleteFilters[deletedIndex]; - } - - // check the updated filter - final updatedIndex = changeset.updateFilters.indexWhere( - (element) => element.filter.id == filterId, + final filters = changeset.filters.items; + final updatedIndex = filters.indexWhere( + (filter) => filter.id == filterId, ); if (updatedIndex != -1) { - _onUpdateNotifier?.value = changeset.updateFilters[updatedIndex].filter; + _onUpdateNotifier?.value = filters[updatedIndex]; } } @@ -122,9 +106,6 @@ class FilterListener { Future stop() async { await _listener?.stop(); - _onDeleteNotifier?.dispose(); - _onDeleteNotifier = null; - _onUpdateNotifier?.dispose(); _onUpdateNotifier = null; } diff --git a/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart b/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart index a44cc13731..4b5277d3eb 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/domain/filter_service.dart @@ -40,12 +40,18 @@ class FilterBackendService { ..condition = condition ..content = content; - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: FieldType.RichText, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: FieldType.RichText, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: FieldType.RichText, + data: filter.writeToBuffer(), + ); } Future> insertCheckboxFilter({ @@ -55,12 +61,18 @@ class FilterBackendService { }) { final filter = CheckboxFilterPB()..condition = condition; - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: FieldType.Checkbox, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: FieldType.Checkbox, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: FieldType.Checkbox, + data: filter.writeToBuffer(), + ); } Future> insertNumberFilter({ @@ -73,12 +85,18 @@ class FilterBackendService { ..condition = condition ..content = content; - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: FieldType.Number, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: FieldType.Number, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: FieldType.Number, + data: filter.writeToBuffer(), + ); } Future> insertDateFilter({ @@ -112,12 +130,18 @@ class FilterBackendService { } } - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: fieldType, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: FieldType.DateTime, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: FieldType.DateTime, + data: filter.writeToBuffer(), + ); } Future> insertURLFilter({ @@ -130,12 +154,18 @@ class FilterBackendService { ..condition = condition ..content = content; - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: FieldType.URL, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: FieldType.URL, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: FieldType.URL, + data: filter.writeToBuffer(), + ); } Future> insertSelectOptionFilter({ @@ -149,12 +179,18 @@ class FilterBackendService { ..condition = condition ..optionIds.addAll(optionIds); - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: fieldType, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: fieldType, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: fieldType, + data: filter.writeToBuffer(), + ); } Future> insertChecklistFilter({ @@ -165,67 +201,94 @@ class FilterBackendService { }) { final filter = ChecklistFilterPB()..condition = condition; - return insertFilter( - fieldId: fieldId, - filterId: filterId, - fieldType: FieldType.Checklist, - data: filter.writeToBuffer(), - ); + return filterId == null + ? insertFilter( + fieldId: fieldId, + fieldType: FieldType.Checklist, + data: filter.writeToBuffer(), + ) + : updateFilter( + filterId: filterId, + fieldId: fieldId, + fieldType: FieldType.Checklist, + data: filter.writeToBuffer(), + ); } Future> insertFilter({ required String fieldId, - String? filterId, required FieldType fieldType, required List data, - }) { - final insertFilterPayload = UpdateFilterPayloadPB.create() + }) async { + final filterData = FilterDataPB() ..fieldId = fieldId ..fieldType = fieldType - ..viewId = viewId ..data = data; - if (filterId != null) { - insertFilterPayload.filterId = filterId; - } + final insertFilterPayload = InsertFilterPB()..data = filterData; - final payload = DatabaseSettingChangesetPB.create() + final payload = DatabaseSettingChangesetPB() ..viewId = viewId - ..updateFilter = insertFilterPayload; - return DatabaseEventUpdateDatabaseSetting(payload).send().then((result) { - return result.fold( - (l) => FlowyResult.success(l), - (err) { - Log.error(err); - return FlowyResult.failure(err); - }, - ); - }); + ..insertFilter = insertFilterPayload; + + final result = await DatabaseEventUpdateDatabaseSetting(payload).send(); + return result.fold( + (l) => FlowyResult.success(l), + (err) { + Log.error(err); + return FlowyResult.failure(err); + }, + ); + } + + Future> updateFilter({ + required String filterId, + required String fieldId, + required FieldType fieldType, + required List data, + }) async { + final filterData = FilterDataPB() + ..fieldId = fieldId + ..fieldType = fieldType + ..data = data; + + final updateFilterPayload = UpdateFilterDataPB() + ..filterId = filterId + ..data = filterData; + + final payload = DatabaseSettingChangesetPB() + ..viewId = viewId + ..updateFilterData = updateFilterPayload; + + final result = await DatabaseEventUpdateDatabaseSetting(payload).send(); + return result.fold( + (l) => FlowyResult.success(l), + (err) { + Log.error(err); + return FlowyResult.failure(err); + }, + ); } Future> deleteFilter({ required String fieldId, required String filterId, - required FieldType fieldType, - }) { - final deleteFilterPayload = DeleteFilterPayloadPB.create() + }) async { + final deleteFilterPayload = DeleteFilterPB() ..fieldId = fieldId - ..filterId = filterId - ..viewId = viewId - ..fieldType = fieldType; + ..filterId = filterId; - final payload = DatabaseSettingChangesetPB.create() + final payload = DatabaseSettingChangesetPB() ..viewId = viewId ..deleteFilter = deleteFilterPayload; - return DatabaseEventUpdateDatabaseSetting(payload).send().then((result) { - return result.fold( - (l) => FlowyResult.success(l), - (err) { - Log.error(err); - return FlowyResult.failure(err); - }, - ); - }); + final result = await DatabaseEventUpdateDatabaseSetting(payload).send(); + return result.fold( + (l) => FlowyResult.success(l), + (err) { + Log.error(err); + return FlowyResult.failure(err); + }, + ); } } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checkbox_filter_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checkbox_filter_editor_bloc.dart index 67c4662b56..17449bda44 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checkbox_filter_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checkbox_filter_editor_bloc.dart @@ -44,7 +44,6 @@ class CheckboxFilterEditorBloc _filterBackendSvc.deleteFilter( fieldId: filterInfo.fieldInfo.id, filterId: filterInfo.filter.id, - fieldType: filterInfo.fieldInfo.fieldType, ); }, didReceiveFilter: (FilterPB filter) { @@ -64,11 +63,10 @@ class CheckboxFilterEditorBloc void _startListening() { _listener.start( - onDeleted: () { - if (!isClosed) add(const CheckboxFilterEditorEvent.delete()); - }, onUpdated: (filter) { - if (!isClosed) add(CheckboxFilterEditorEvent.didReceiveFilter(filter)); + if (!isClosed) { + add(CheckboxFilterEditorEvent.didReceiveFilter(filter)); + } }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checklist_filter_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checklist_filter_bloc.dart index 5b91cd9195..1decdd8215 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checklist_filter_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/checklist_filter_bloc.dart @@ -44,7 +44,6 @@ class ChecklistFilterEditorBloc _filterBackendSvc.deleteFilter( fieldId: filterInfo.fieldInfo.id, filterId: filterInfo.filter.id, - fieldType: filterInfo.fieldInfo.fieldType, ); }, didReceiveFilter: (FilterPB filter) { @@ -64,9 +63,6 @@ class ChecklistFilterEditorBloc void _startListening() { _listener.start( - onDeleted: () { - if (!isClosed) add(const ChecklistFilterEditorEvent.delete()); - }, onUpdated: (filter) { if (!isClosed) { add(ChecklistFilterEditorEvent.didReceiveFilter(filter)); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/number_filter_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/number_filter_editor_bloc.dart index 5b1de6d782..d68dd17537 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/number_filter_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/number_filter_editor_bloc.dart @@ -59,7 +59,6 @@ class NumberFilterEditorBloc _filterBackendSvc.deleteFilter( fieldId: filterInfo.fieldInfo.id, filterId: filterInfo.filter.id, - fieldType: filterInfo.fieldInfo.fieldType, ); }, ); @@ -69,11 +68,6 @@ class NumberFilterEditorBloc void _startListening() { _listener.start( - onDeleted: () { - if (!isClosed) { - add(const NumberFilterEditorEvent.delete()); - } - }, onUpdated: (filter) { if (!isClosed) { add(NumberFilterEditorEvent.didReceiveFilter(filter)); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart index cc92d2d906..9a7ab131b9 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/select_option_filter_bloc.dart @@ -60,7 +60,6 @@ class SelectOptionFilterEditorBloc _filterBackendSvc.deleteFilter( fieldId: filterInfo.fieldInfo.id, filterId: filterInfo.filter.id, - fieldType: filterInfo.fieldInfo.fieldType, ); }, didReceiveFilter: (FilterPB filter) { @@ -83,9 +82,6 @@ class SelectOptionFilterEditorBloc void _startListening() { _listener.start( - onDeleted: () { - if (!isClosed) add(const SelectOptionFilterEditorEvent.delete()); - }, onUpdated: (filter) { if (!isClosed) { add(SelectOptionFilterEditorEvent.didReceiveFilter(filter)); diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/text_filter_editor_bloc.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/text_filter_editor_bloc.dart index 1c2791d8d0..54563010a0 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/text_filter_editor_bloc.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/application/filter/text_filter_editor_bloc.dart @@ -53,7 +53,6 @@ class TextFilterEditorBloc _filterBackendSvc.deleteFilter( fieldId: filterInfo.fieldInfo.id, filterId: filterInfo.filter.id, - fieldType: filterInfo.fieldInfo.fieldType, ); }, didReceiveFilter: (FilterPB filter) { @@ -73,11 +72,10 @@ class TextFilterEditorBloc void _startListening() { _listener.start( - onDeleted: () { - if (!isClosed) add(const TextFilterEditorEvent.delete()); - }, onUpdated: (filter) { - if (!isClosed) add(TextFilterEditorEvent.didReceiveFilter(filter)); + if (!isClosed) { + add(TextFilterEditorEvent.didReceiveFilter(filter)); + } }, ); } diff --git a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_info.dart b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_info.dart index 97fc590748..9adc642c3b 100644 --- a/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_info.dart +++ b/frontend/appflowy_flutter/lib/plugins/database/grid/presentation/widgets/filter/filter_info.dart @@ -18,42 +18,42 @@ class FilterInfo { String get filterId => filter.id; - String get fieldId => filter.fieldId; + String get fieldId => filter.data.fieldId; DateFilterPB? dateFilter() { - return filter.fieldType == FieldType.DateTime - ? DateFilterPB.fromBuffer(filter.data) + return filter.data.fieldType == FieldType.DateTime + ? DateFilterPB.fromBuffer(filter.data.data) : null; } TextFilterPB? textFilter() { - return filter.fieldType == FieldType.RichText - ? TextFilterPB.fromBuffer(filter.data) + return filter.data.fieldType == FieldType.RichText + ? TextFilterPB.fromBuffer(filter.data.data) : null; } CheckboxFilterPB? checkboxFilter() { - return filter.fieldType == FieldType.Checkbox - ? CheckboxFilterPB.fromBuffer(filter.data) + return filter.data.fieldType == FieldType.Checkbox + ? CheckboxFilterPB.fromBuffer(filter.data.data) : null; } SelectOptionFilterPB? selectOptionFilter() { - return filter.fieldType == FieldType.SingleSelect || - filter.fieldType == FieldType.MultiSelect - ? SelectOptionFilterPB.fromBuffer(filter.data) + return filter.data.fieldType == FieldType.SingleSelect || + filter.data.fieldType == FieldType.MultiSelect + ? SelectOptionFilterPB.fromBuffer(filter.data.data) : null; } ChecklistFilterPB? checklistFilter() { - return filter.fieldType == FieldType.Checklist - ? ChecklistFilterPB.fromBuffer(filter.data) + return filter.data.fieldType == FieldType.Checklist + ? ChecklistFilterPB.fromBuffer(filter.data.data) : null; } NumberFilterPB? numberFilter() { - return filter.fieldType == FieldType.Number - ? NumberFilterPB.fromBuffer(filter.data) + return filter.data.fieldType == FieldType.Number + ? NumberFilterPB.fromBuffer(filter.data.data) : null; } } diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart index e27f9b6b6c..d02a319ab1 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/create_filter_test.dart @@ -42,7 +42,6 @@ void main() { await service.deleteFilter( fieldId: textField.id, filterId: filterInfo.filter.id, - fieldType: textField.fieldType, ); await gridResponseFuture(); diff --git a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart index 432fd339ae..0e4c79a4e7 100644 --- a/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart +++ b/frontend/appflowy_flutter/test/bloc_test/grid_test/filter/filter_rows_by_text_test.dart @@ -37,7 +37,6 @@ void main() { await service.deleteFilter( fieldId: textField.id, filterId: textFilter.filter.id, - fieldType: textField.fieldType, ); await gridResponseFuture(); assert(context.rowInfos.length == 3); @@ -65,7 +64,6 @@ void main() { await service.deleteFilter( fieldId: textField.id, filterId: textFilter.filter.id, - fieldType: textField.fieldType, ); await gridResponseFuture(); assert(context.rowInfos.length == 3); @@ -107,7 +105,6 @@ void main() { await service.deleteFilter( fieldId: textField.id, filterId: textFilter.filter.id, - fieldType: textField.fieldType, ); await gridResponseFuture(); assert(context.rowInfos.length == 3); @@ -165,7 +162,6 @@ void main() { await service.deleteFilter( fieldId: textField.id, filterId: textFilter.filter.id, - fieldType: textField.fieldType, ); await gridResponseFuture(); assert(context.rowInfos.length == 3); diff --git a/frontend/appflowy_tauri/src-tauri/Cargo.toml b/frontend/appflowy_tauri/src-tauri/Cargo.toml index aaf839df34..0a9926c194 100644 --- a/frontend/appflowy_tauri/src-tauri/Cargo.toml +++ b/frontend/appflowy_tauri/src-tauri/Cargo.toml @@ -34,7 +34,8 @@ lru = "0.12.0" [dependencies] serde_json.workspace = true serde.workspace = true -tauri = { version = "1.5", features = [ "dialog-all", +tauri = { version = "1.5", features = [ + "dialog-all", "clipboard-all", "fs-all", "shell-open", diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts index 33cea9c45a..323f8dac82 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_listeners.ts @@ -1,35 +1,8 @@ import { Database, pbToFilter } from '$app/application/database'; import { FilterChangesetNotificationPB } from '@/services/backend'; -const deleteFiltersFromChange = (database: Database, changeset: FilterChangesetNotificationPB) => { - const deleteIds = changeset.delete_filters.map((pb) => pb.id); - - if (deleteIds.length) { - database.filters = database.filters.filter((item) => !deleteIds.includes(item.id)); - } -}; - -const insertFiltersFromChange = (database: Database, changeset: FilterChangesetNotificationPB) => { - changeset.insert_filters.forEach((pb) => { - database.filters.push(pbToFilter(pb)); - }); -}; - -const updateFiltersFromChange = (database: Database, changeset: FilterChangesetNotificationPB) => { - changeset.update_filters.forEach((pb) => { - const found = database.filters.find((item) => item.id === pb.filter_id); - - if (found) { - const newFilter = pbToFilter(pb.filter); - - Object.assign(found, newFilter); - database.filters = [...database.filters]; - } - }); -}; - export const didUpdateFilter = (database: Database, changeset: FilterChangesetNotificationPB) => { - deleteFiltersFromChange(database, changeset); - insertFiltersFromChange(database, changeset); - updateFiltersFromChange(database, changeset); + const filters = changeset.filters.items.map((pb) => pbToFilter(pb)); + + database.filters = filters; }; diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts index 97fcb6e505..6283763d28 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_service.ts @@ -21,22 +21,20 @@ export async function insertFilter({ fieldId, fieldType, data, - filterId, }: { viewId: string; fieldId: string; fieldType: FieldType; data?: UndeterminedFilter['data']; - filterId?: string; }): Promise { const payload = DatabaseSettingChangesetPB.fromObject({ view_id: viewId, - update_filter: { - view_id: viewId, - field_id: fieldId, - field_type: fieldType, - filter_id: filterId, - data: data ? filterDataToPB(data, fieldType)?.serialize() : undefined, + insert_filter: { + data: { + field_id: fieldId, + field_type: fieldType, + data: data ? filterDataToPB(data, fieldType)?.serialize() : undefined, + }, }, }); @@ -52,12 +50,13 @@ export async function insertFilter({ export async function updateFilter(viewId: string, filter: UndeterminedFilter): Promise { const payload = DatabaseSettingChangesetPB.fromObject({ view_id: viewId, - update_filter: { - view_id: viewId, + update_filter_data: { filter_id: filter.id, - field_id: filter.fieldId, - field_type: filter.fieldType, - data: filterDataToPB(filter.data, filter.fieldType)?.serialize(), + data: { + field_id: filter.fieldId, + field_type: filter.fieldType, + data: filterDataToPB(filter.data, filter.fieldType)?.serialize(), + }, }, }); @@ -74,10 +73,8 @@ export async function deleteFilter(viewId: string, filter: Omit) const payload = DatabaseSettingChangesetPB.fromObject({ view_id: viewId, delete_filter: { - view_id: viewId, filter_id: filter.id, field_id: filter.fieldId, - field_type: filter.fieldType, }, }); diff --git a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts index 9e6f9f87ce..9c83ff01e0 100644 --- a/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts +++ b/frontend/appflowy_tauri/src/appflowy_app/application/database/filter/filter_types.ts @@ -195,8 +195,8 @@ export function bytesToFilterData(bytes: Uint8Array, fieldType: FieldType) { export function pbToFilter(pb: FilterPB): Filter { return { id: pb.id, - fieldId: pb.field_id, - fieldType: pb.field_type, - data: bytesToFilterData(pb.data, pb.field_type), + fieldId: pb.data.field_id, + fieldType: pb.data.field_type, + data: bytesToFilterData(pb.data.data, pb.data.field_type), }; } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checkbox_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checkbox_filter.rs index 4b2f9fb888..6dde92ac3d 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checkbox_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checkbox_filter.rs @@ -1,7 +1,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::filter::ParseFilterData; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct CheckboxFilterPB { @@ -9,9 +9,8 @@ pub struct CheckboxFilterPB { pub condition: CheckboxFilterConditionPB, } -#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)] +#[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)] #[repr(u8)] -#[derive(Default)] pub enum CheckboxFilterConditionPB { #[default] IsChecked = 0, @@ -24,7 +23,7 @@ impl std::convert::From for u32 { } } -impl std::convert::TryFrom for CheckboxFilterConditionPB { +impl TryFrom for CheckboxFilterConditionPB { type Error = ErrorCode; fn try_from(value: u8) -> Result { @@ -36,22 +35,10 @@ impl std::convert::TryFrom for CheckboxFilterConditionPB { } } -impl FromFilterString for CheckboxFilterPB { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized, - { +impl ParseFilterData for CheckboxFilterPB { + fn parse(condition: u8, _content: String) -> Self { CheckboxFilterPB { - condition: CheckboxFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(CheckboxFilterConditionPB::IsChecked), - } - } -} - -impl std::convert::From<&Filter> for CheckboxFilterPB { - fn from(filter: &Filter) -> Self { - CheckboxFilterPB { - condition: CheckboxFilterConditionPB::try_from(filter.condition as u8) + condition: CheckboxFilterConditionPB::try_from(condition) .unwrap_or(CheckboxFilterConditionPB::IsChecked), } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checklist_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checklist_filter.rs index 0c2e7fc037..4acb3a9941 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checklist_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/checklist_filter.rs @@ -1,7 +1,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::filter::ParseFilterData; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct ChecklistFilterPB { @@ -36,22 +36,10 @@ impl std::convert::TryFrom for ChecklistFilterConditionPB { } } -impl FromFilterString for ChecklistFilterPB { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized, - { - ChecklistFilterPB { - condition: ChecklistFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), - } - } -} - -impl std::convert::From<&Filter> for ChecklistFilterPB { - fn from(filter: &Filter) -> Self { - ChecklistFilterPB { - condition: ChecklistFilterConditionPB::try_from(filter.condition as u8) +impl ParseFilterData for ChecklistFilterPB { + fn parse(condition: u8, _content: String) -> Self { + Self { + condition: ChecklistFilterConditionPB::try_from(condition) .unwrap_or(ChecklistFilterConditionPB::IsIncomplete), } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/date_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/date_filter.rs index 75891cea6f..ed302af51e 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/date_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/date_filter.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::filter::ParseFilterData; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct DateFilterPB { @@ -79,37 +79,17 @@ impl std::convert::TryFrom for DateFilterConditionPB { } } } -impl FromFilterString for DateFilterPB { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized, - { - let condition = DateFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(DateFilterConditionPB::DateIs); - let mut date_filter = DateFilterPB { + +impl ParseFilterData for DateFilterPB { + fn parse(condition: u8, content: String) -> Self { + let condition = + DateFilterConditionPB::try_from(condition).unwrap_or(DateFilterConditionPB::DateIs); + let mut date_filter = Self { condition, ..Default::default() }; - if let Ok(content) = DateFilterContentPB::from_str(&filter.content) { - date_filter.start = content.start; - date_filter.end = content.end; - date_filter.timestamp = content.timestamp; - }; - - date_filter - } -} -impl std::convert::From<&Filter> for DateFilterPB { - fn from(filter: &Filter) -> Self { - let condition = DateFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(DateFilterConditionPB::DateIs); - let mut date_filter = DateFilterPB { - condition, - ..Default::default() - }; - - if let Ok(content) = DateFilterContentPB::from_str(&filter.content) { + if let Ok(content) = DateFilterContentPB::from_str(&content) { date_filter.start = content.start; date_filter.end = content.end; date_filter.timestamp = content.timestamp; diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/filter_changeset.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/filter_changeset.rs index 05a0fbd4ea..06f18be289 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/filter_changeset.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/filter_changeset.rs @@ -1,54 +1,22 @@ -use crate::entities::FilterPB; use flowy_derive::ProtoBuf; +use crate::entities::RepeatedFilterPB; +use crate::services::filter::Filter; + #[derive(Debug, Default, ProtoBuf)] pub struct FilterChangesetNotificationPB { #[pb(index = 1)] pub view_id: String, #[pb(index = 2)] - pub insert_filters: Vec, - - #[pb(index = 3)] - pub delete_filters: Vec, - - #[pb(index = 4)] - pub update_filters: Vec, -} - -#[derive(Debug, Default, ProtoBuf)] -pub struct UpdatedFilter { - #[pb(index = 1)] - pub filter_id: String, - - #[pb(index = 2, one_of)] - pub filter: Option, + pub filters: RepeatedFilterPB, } impl FilterChangesetNotificationPB { - pub fn from_insert(view_id: &str, filters: Vec) -> Self { + pub fn from_filters(view_id: &str, filters: &Vec) -> Self { Self { view_id: view_id.to_string(), - insert_filters: filters, - delete_filters: Default::default(), - update_filters: Default::default(), - } - } - pub fn from_delete(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: Default::default(), - delete_filters: filters, - update_filters: Default::default(), - } - } - - pub fn from_update(view_id: &str, filters: Vec) -> Self { - Self { - view_id: view_id.to_string(), - insert_filters: Default::default(), - delete_filters: Default::default(), - update_filters: filters, + filters: filters.into(), } } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/number_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/number_filter.rs index 17f43c8640..6ea5e9ac7e 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/number_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/number_filter.rs @@ -1,7 +1,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::filter::ParseFilterData; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct NumberFilterPB { @@ -49,24 +49,12 @@ impl std::convert::TryFrom for NumberFilterConditionPB { } } -impl FromFilterString for NumberFilterPB { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized, - { +impl ParseFilterData for NumberFilterPB { + fn parse(condition: u8, content: String) -> Self { NumberFilterPB { - condition: NumberFilterConditionPB::try_from(filter.condition as u8) + condition: NumberFilterConditionPB::try_from(condition) .unwrap_or(NumberFilterConditionPB::Equal), - content: filter.content.clone(), - } - } -} -impl std::convert::From<&Filter> for NumberFilterPB { - fn from(filter: &Filter) -> Self { - NumberFilterPB { - condition: NumberFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(NumberFilterConditionPB::Equal), - content: filter.content.clone(), + content, } } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/relation_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/relation_filter.rs index 7c6e6ce11b..202f7a316b 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/relation_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/relation_filter.rs @@ -1,6 +1,6 @@ use flowy_derive::ProtoBuf; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::filter::ParseFilterData; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RelationFilterPB { @@ -8,17 +8,8 @@ pub struct RelationFilterPB { pub condition: i64, } -impl FromFilterString for RelationFilterPB { - fn from_filter(_filter: &Filter) -> Self - where - Self: Sized, - { - RelationFilterPB { condition: 0 } - } -} - -impl From<&Filter> for RelationFilterPB { - fn from(_filter: &Filter) -> Self { +impl ParseFilterData for RelationFilterPB { + fn parse(_condition: u8, _content: String) -> Self { RelationFilterPB { condition: 0 } } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs index a7e8cbb60a..59261b1920 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/select_option_filter.rs @@ -3,8 +3,7 @@ use std::str::FromStr; use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use crate::services::field::SelectOptionIds; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::{field::SelectOptionIds, filter::ParseFilterData}; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct SelectOptionFilterPB { @@ -45,27 +44,14 @@ impl std::convert::TryFrom for SelectOptionConditionPB { } } } -impl FromFilterString for SelectOptionFilterPB { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized, - { - let ids = SelectOptionIds::from_str(&filter.content).unwrap_or_default(); - SelectOptionFilterPB { - condition: SelectOptionConditionPB::try_from(filter.condition as u8) +impl ParseFilterData for SelectOptionFilterPB { + fn parse(condition: u8, content: String) -> Self { + Self { + condition: SelectOptionConditionPB::try_from(condition) .unwrap_or(SelectOptionConditionPB::OptionIs), - option_ids: ids.into_inner(), - } - } -} - -impl std::convert::From<&Filter> for SelectOptionFilterPB { - fn from(filter: &Filter) -> Self { - let ids = SelectOptionIds::from_str(&filter.content).unwrap_or_default(); - SelectOptionFilterPB { - condition: SelectOptionConditionPB::try_from(filter.condition as u8) - .unwrap_or(SelectOptionConditionPB::OptionIs), - option_ids: ids.into_inner(), + option_ids: SelectOptionIds::from_str(&content) + .unwrap_or_default() + .into_inner(), } } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs index 0956fdb894..5eb13d037b 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/text_filter.rs @@ -1,7 +1,7 @@ use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; -use crate::services::filter::{Filter, FromFilterString}; +use crate::services::filter::ParseFilterData; #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct TextFilterPB { @@ -51,25 +51,11 @@ impl std::convert::TryFrom for TextFilterConditionPB { } } -impl FromFilterString for TextFilterPB { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized, - { - TextFilterPB { - condition: TextFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(TextFilterConditionPB::Is), - content: filter.content.clone(), - } - } -} - -impl std::convert::From<&Filter> for TextFilterPB { - fn from(filter: &Filter) -> Self { - TextFilterPB { - condition: TextFilterConditionPB::try_from(filter.condition as u8) - .unwrap_or(TextFilterConditionPB::Is), - content: filter.content.clone(), +impl ParseFilterData for TextFilterPB { + fn parse(condition: u8, content: String) -> Self { + Self { + condition: TextFilterConditionPB::try_from(condition).unwrap_or(TextFilterConditionPB::Is), + content, } } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/util.rs b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/util.rs index cdeef0401c..6d48abf0e8 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/filter_entities/util.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/filter_entities/util.rs @@ -1,216 +1,274 @@ use std::convert::TryInto; -use std::sync::Arc; use bytes::Bytes; -use collab_database::fields::Field; -use flowy_derive::ProtoBuf; +use flowy_derive::{ProtoBuf, ProtoBuf_Enum}; use flowy_error::ErrorCode; +use lib_infra::box_any::BoxAny; +use protobuf::ProtobufError; use validator::Validate; -use crate::entities::parser::NotEmptyStr; use crate::entities::{ - CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType, - NumberFilterPB, RelationFilterPB, SelectOptionFilterPB, TextFilterPB, + CheckboxFilterPB, ChecklistFilterPB, DateFilterPB, FieldType, NumberFilterPB, RelationFilterPB, + SelectOptionFilterPB, TextFilterPB, }; -use crate::services::field::SelectOptionIds; -use crate::services::filter::Filter; +use crate::services::filter::{Filter, FilterChangeset, FilterInner}; -#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] +#[derive(Debug, Default, Clone, ProtoBuf_Enum, Eq, PartialEq, Copy)] +#[repr(u8)] +pub enum FilterType { + #[default] + Data = 0, + And = 1, + Or = 2, +} + +impl From<&FilterInner> for FilterType { + fn from(value: &FilterInner) -> Self { + match value { + FilterInner::And { .. } => Self::And, + FilterInner::Or { .. } => Self::Or, + FilterInner::Data { .. } => Self::Data, + } + } +} + +#[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)] pub struct FilterPB { #[pb(index = 1)] pub id: String, #[pb(index = 2)] - pub field_id: String, + pub filter_type: FilterType, #[pb(index = 3)] + pub children: Vec, + + #[pb(index = 4, one_of)] + pub data: Option, +} + +#[derive(Debug, Default, Clone, ProtoBuf, Eq, PartialEq)] +pub struct FilterDataPB { + #[pb(index = 1)] + pub field_id: String, + + #[pb(index = 2)] pub field_type: FieldType, - #[pb(index = 4)] + #[pb(index = 3)] pub data: Vec, } -impl std::convert::From<&Filter> for FilterPB { +impl From<&Filter> for FilterPB { fn from(filter: &Filter) -> Self { - let bytes: Bytes = match filter.field_type { - FieldType::RichText => TextFilterPB::from(filter).try_into().unwrap(), - FieldType::Number => NumberFilterPB::from(filter).try_into().unwrap(), - FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => { - DateFilterPB::from(filter).try_into().unwrap() + match &filter.inner { + FilterInner::And { children } | FilterInner::Or { children } => Self { + id: filter.id.clone(), + filter_type: FilterType::from(&filter.inner), + children: children.iter().map(FilterPB::from).collect(), + data: None, + }, + FilterInner::Data { + field_id, + field_type, + condition_and_content, + } => { + let bytes: Result = match field_type { + FieldType::RichText | FieldType::URL => condition_and_content + .cloned::() + .unwrap() + .try_into(), + FieldType::Number => condition_and_content + .cloned::() + .unwrap() + .try_into(), + FieldType::DateTime | FieldType::CreatedTime | FieldType::LastEditedTime => { + condition_and_content + .cloned::() + .unwrap() + .try_into() + }, + FieldType::SingleSelect | FieldType::MultiSelect => condition_and_content + .cloned::() + .unwrap() + .try_into(), + FieldType::Checklist => condition_and_content + .cloned::() + .unwrap() + .try_into(), + FieldType::Checkbox => condition_and_content + .cloned::() + .unwrap() + .try_into(), + + FieldType::Relation => condition_and_content + .cloned::() + .unwrap() + .try_into(), + }; + + Self { + id: filter.id.clone(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: field_id.clone(), + field_type: *field_type, + data: bytes.unwrap().to_vec(), + }), + } }, - FieldType::SingleSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(), - FieldType::MultiSelect => SelectOptionFilterPB::from(filter).try_into().unwrap(), - FieldType::Checklist => ChecklistFilterPB::from(filter).try_into().unwrap(), - FieldType::Checkbox => CheckboxFilterPB::from(filter).try_into().unwrap(), - FieldType::URL => TextFilterPB::from(filter).try_into().unwrap(), - FieldType::Relation => RelationFilterPB::from(filter).try_into().unwrap(), - }; - Self { - id: filter.id.clone(), - field_id: filter.field_id.clone(), - field_type: filter.field_type, - data: bytes.to_vec(), } } } +impl TryFrom for FilterInner { + type Error = ErrorCode; + + fn try_from(value: FilterDataPB) -> Result { + let bytes: &[u8] = value.data.as_ref(); + let condition_and_content = match value.field_type { + FieldType::RichText | FieldType::URL => { + BoxAny::new(TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + FieldType::Checkbox => { + BoxAny::new(CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + FieldType::Number => { + BoxAny::new(NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => { + BoxAny::new(DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + FieldType::SingleSelect | FieldType::MultiSelect => { + BoxAny::new(SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + FieldType::Checklist => { + BoxAny::new(ChecklistFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + FieldType::Relation => { + BoxAny::new(RelationFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?) + }, + }; + + Ok(Self::Data { + field_id: value.field_id, + field_type: value.field_type, + condition_and_content, + }) + } +} + #[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)] pub struct RepeatedFilterPB { #[pb(index = 1)] pub items: Vec, } -impl std::convert::From>> for RepeatedFilterPB { - fn from(filters: Vec>) -> Self { +impl From<&Vec> for RepeatedFilterPB { + fn from(filters: &Vec) -> Self { RepeatedFilterPB { - items: filters.into_iter().map(|rev| rev.as_ref().into()).collect(), + items: filters.iter().map(|filter| filter.into()).collect(), } } } -impl std::convert::From> for RepeatedFilterPB { +impl From> for RepeatedFilterPB { fn from(items: Vec) -> Self { Self { items } } } #[derive(ProtoBuf, Debug, Default, Clone, Validate)] -pub struct DeleteFilterPayloadPB { - #[pb(index = 1)] - #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] - pub field_id: String, +pub struct InsertFilterPB { + /// If None, the filter will be the root of a new filter tree + #[pb(index = 1, one_of)] + #[validate(custom = "crate::entities::utils::validate_filter_id")] + pub parent_filter_id: Option, #[pb(index = 2)] - pub field_type: FieldType, - - #[pb(index = 3)] - #[validate(custom = "crate::entities::utils::validate_filter_id")] - pub filter_id: String, - - #[pb(index = 4)] - #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] - pub view_id: String, + pub data: FilterDataPB, } #[derive(ProtoBuf, Debug, Default, Clone, Validate)] -pub struct UpdateFilterPayloadPB { +pub struct UpdateFilterTypePB { #[pb(index = 1)] - #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] - pub field_id: String, + #[validate(custom = "crate::entities::utils::validate_filter_id")] + pub filter_id: String, #[pb(index = 2)] - pub field_type: FieldType, + pub filter_type: FilterType, +} - /// Create a new filter if the filter_id is None - #[pb(index = 3, one_of)] +#[derive(ProtoBuf, Debug, Default, Clone, Validate)] +pub struct UpdateFilterDataPB { + #[pb(index = 1)] #[validate(custom = "crate::entities::utils::validate_filter_id")] - pub filter_id: Option, + pub filter_id: String, - #[pb(index = 4)] - pub data: Vec, + #[pb(index = 2)] + pub data: FilterDataPB, +} - #[pb(index = 5)] +#[derive(ProtoBuf, Debug, Default, Clone, Validate)] +pub struct DeleteFilterPB { + #[pb(index = 1)] + #[validate(custom = "crate::entities::utils::validate_filter_id")] + pub filter_id: String, + + #[pb(index = 2)] #[validate(custom = "lib_infra::validator_fn::required_not_empty_str")] - pub view_id: String, + pub field_id: String, } -impl UpdateFilterPayloadPB { - #[allow(dead_code)] - pub fn new>( - view_id: &str, - field: &Field, - data: T, - ) -> Self { - let data = data.try_into().unwrap_or_else(|_| Bytes::new()); - let field_type = FieldType::from(field.field_type); - Self { - view_id: view_id.to_owned(), - field_id: field.id.clone(), - field_type, - filter_id: None, - data: data.to_vec(), - } - } -} - -impl TryInto for UpdateFilterPayloadPB { +impl TryFrom for FilterChangeset { type Error = ErrorCode; - fn try_into(self) -> Result { - let view_id = NotEmptyStr::parse(self.view_id) - .map_err(|_| ErrorCode::DatabaseViewIdIsEmpty)? - .0; - - let field_id = NotEmptyStr::parse(self.field_id) - .map_err(|_| ErrorCode::FieldIdIsEmpty)? - .0; - let filter_id = match self.filter_id { - None => None, - Some(filter_id) => Some( - NotEmptyStr::parse(filter_id) - .map_err(|_| ErrorCode::FilterIdIsEmpty)? - .0, - ), + fn try_from(value: InsertFilterPB) -> Result { + let changeset = Self::Insert { + parent_filter_id: value.parent_filter_id, + data: value.data.try_into()?, }; - let condition; - let mut content = "".to_string(); - let bytes: &[u8] = self.data.as_ref(); - match self.field_type { - FieldType::RichText | FieldType::URL => { - let filter = TextFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = filter.content; - }, - FieldType::Checkbox => { - let filter = CheckboxFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - }, - FieldType::Number => { - let filter = NumberFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = filter.content; - }, - FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => { - let filter = DateFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = DateFilterContentPB { - start: filter.start, - end: filter.end, - timestamp: filter.timestamp, - } - .to_string(); - }, - FieldType::SingleSelect | FieldType::MultiSelect | FieldType::Checklist => { - let filter = SelectOptionFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - content = SelectOptionIds::from(filter.option_ids).to_string(); - }, - FieldType::Relation => { - let filter = RelationFilterPB::try_from(bytes).map_err(|_| ErrorCode::ProtobufSerde)?; - condition = filter.condition as u8; - }, - } - - Ok(UpdateFilterParams { - view_id, - field_id, - filter_id, - field_type: self.field_type, - condition: condition as i64, - content, - }) + Ok(changeset) } } -#[derive(Debug)] -pub struct UpdateFilterParams { - pub view_id: String, - pub field_id: String, - /// Create a new filter if the filter_id is None - pub filter_id: Option, - pub field_type: FieldType, - pub condition: i64, - pub content: String, +impl TryFrom for FilterChangeset { + type Error = ErrorCode; + + fn try_from(value: UpdateFilterDataPB) -> Result { + let changeset = Self::UpdateData { + filter_id: value.filter_id, + data: value.data.try_into()?, + }; + + Ok(changeset) + } +} + +impl TryFrom for FilterChangeset { + type Error = ErrorCode; + + fn try_from(value: UpdateFilterTypePB) -> Result { + if matches!(value.filter_type, FilterType::Data) { + return Err(ErrorCode::InvalidParams); + } + + let changeset = Self::UpdateType { + filter_id: value.filter_id, + filter_type: value.filter_type, + }; + Ok(changeset) + } +} + +impl From for FilterChangeset { + fn from(value: DeleteFilterPB) -> Self { + Self::Delete { + filter_id: value.filter_id, + field_id: value.field_id, + } + } } diff --git a/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs b/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs index e1cefc07cc..2cb2f3613d 100644 --- a/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs +++ b/frontend/rust-lib/flowy-database2/src/entities/setting_entities.rs @@ -9,9 +9,9 @@ use validator::Validate; use crate::entities::parser::NotEmptyStr; use crate::entities::{ - CalendarLayoutSettingPB, DeleteFilterPayloadPB, DeleteSortPayloadPB, RepeatedFieldSettingsPB, - RepeatedFilterPB, RepeatedGroupSettingPB, RepeatedSortPB, UpdateFilterPayloadPB, UpdateGroupPB, - UpdateSortPayloadPB, + CalendarLayoutSettingPB, DeleteFilterPB, DeleteSortPayloadPB, InsertFilterPB, + RepeatedFieldSettingsPB, RepeatedFilterPB, RepeatedGroupSettingPB, RepeatedSortPB, + UpdateFilterDataPB, UpdateFilterTypePB, UpdateGroupPB, UpdateSortPayloadPB, }; use crate::services::setting::{BoardLayoutSetting, CalendarLayoutSetting}; @@ -79,26 +79,34 @@ pub struct DatabaseSettingChangesetPB { #[pb(index = 3, one_of)] #[validate] - pub update_filter: Option, + pub insert_filter: Option, #[pb(index = 4, one_of)] #[validate] - pub delete_filter: Option, + pub update_filter_type: Option, #[pb(index = 5, one_of)] #[validate] - pub update_group: Option, + pub update_filter_data: Option, #[pb(index = 6, one_of)] #[validate] - pub update_sort: Option, + pub delete_filter: Option, #[pb(index = 7, one_of)] #[validate] - pub reorder_sort: Option, + pub update_group: Option, #[pb(index = 8, one_of)] #[validate] + pub update_sort: Option, + + #[pb(index = 9, one_of)] + #[validate] + pub reorder_sort: Option, + + #[pb(index = 10, one_of)] + #[validate] pub delete_sort: Option, } diff --git a/frontend/rust-lib/flowy-database2/src/event_handler.rs b/frontend/rust-lib/flowy-database2/src/event_handler.rs index e14908b090..c58e825af5 100644 --- a/frontend/rust-lib/flowy-database2/src/event_handler.rs +++ b/frontend/rust-lib/flowy-database2/src/event_handler.rs @@ -91,14 +91,28 @@ pub(crate) async fn update_database_setting_handler( let params = data.try_into_inner()?; let database_editor = manager.get_database_with_view_id(¶ms.view_id).await?; - if let Some(update_filter) = params.update_filter { + if let Some(payload) = params.insert_filter { database_editor - .create_or_update_filter(update_filter.try_into()?) + .modify_view_filters(¶ms.view_id, payload.try_into()?) .await?; } - if let Some(delete_filter) = params.delete_filter { - database_editor.delete_filter(delete_filter).await?; + if let Some(payload) = params.update_filter_type { + database_editor + .modify_view_filters(¶ms.view_id, payload.try_into()?) + .await?; + } + + if let Some(payload) = params.update_filter_data { + database_editor + .modify_view_filters(¶ms.view_id, payload.try_into()?) + .await?; + } + + if let Some(payload) = params.delete_filter { + database_editor + .modify_view_filters(¶ms.view_id, payload.into()) + .await?; } if let Some(update_sort) = params.update_sort { diff --git a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs index dad31a12f0..9c0c1b1713 100644 --- a/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs +++ b/frontend/rust-lib/flowy-database2/src/services/calculations/service.rs @@ -36,8 +36,8 @@ impl CalculationsService { let mut sum = 0.0; let mut len = 0.0; let field_type = FieldType::from(field.field_type); - if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(&field_type) + if let Some(handler) = + TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type) { for row_cell in row_cells { if let Some(cell) = &row_cell.cell { @@ -131,8 +131,8 @@ impl CalculationsService { fn calculate_count_empty(&self, field: &Field, row_cells: Vec>) -> String { let field_type = FieldType::from(field.field_type); - if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(&field_type) + if let Some(handler) = + TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type) { if !row_cells.is_empty() { return format!( @@ -154,8 +154,8 @@ impl CalculationsService { fn calculate_count_non_empty(&self, field: &Field, row_cells: Vec>) -> String { let field_type = FieldType::from(field.field_type); - if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(&field_type) + if let Some(handler) = + TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type) { if !row_cells.is_empty() { return format!( @@ -183,8 +183,8 @@ impl CalculationsService { let mut values = vec![]; let field_type = FieldType::from(field.field_type); - if let Some(handler) = TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(&field_type) + if let Some(handler) = + TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(&field_type) { for row_cell in row_cells { if let Some(cell) = &row_cell.cell { diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs index 30e61dd098..07864351d4 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_data_cache.rs @@ -4,4 +4,3 @@ use std::sync::Arc; use crate::utils::cache::AnyTypeCache; pub type CellCache = Arc>>; -pub type CellFilterCache = Arc>>; diff --git a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs index 4caafafef0..4d9140c2b6 100644 --- a/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/cell/cell_operation.rs @@ -75,7 +75,7 @@ pub fn apply_cell_changeset( cell_data_cache: Option, ) -> Result { let field_type = FieldType::from(field.field_type); - match TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache) + match TypeOptionCellExt::new(field, cell_data_cache) .get_type_option_cell_data_handler(&field_type) { None => Ok(Cell::default()), @@ -128,7 +128,7 @@ pub fn try_decode_cell_to_cell_protobuf( field: &Field, cell_data_cache: Option, ) -> FlowyResult { - match TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache) + match TypeOptionCellExt::new(field, cell_data_cache) .get_type_option_cell_data_handler(to_field_type) { None => Ok(CellProtobufBlob::default()), @@ -143,7 +143,7 @@ pub fn try_decode_cell_to_cell_data( field: &Field, cell_data_cache: Option, ) -> Option { - let handler = TypeOptionCellExt::new_with_cell_data_cache(field, cell_data_cache) + let handler = TypeOptionCellExt::new(field, cell_data_cache) .get_type_option_cell_data_handler(to_field_type)?; handler .get_cell_data(cell, from_field_type, field) @@ -152,8 +152,8 @@ pub fn try_decode_cell_to_cell_data( } /// Returns a string that represents the current field_type's cell data. -/// For example, The string of the Multi-Select cell will be a list of the option's name -/// separated by a comma. +/// For example, a Multi-Select cell will be represented by a list of the options' names +/// separated by commas. /// /// # Arguments /// @@ -162,16 +162,13 @@ pub fn try_decode_cell_to_cell_data( /// * `from_field_type`: the original field type of the passed-in cell data. /// * `field`: used to get the corresponding TypeOption for the specified field type. /// -/// returns: String pub fn stringify_cell_data( cell: &Cell, to_field_type: &FieldType, from_field_type: &FieldType, field: &Field, ) -> String { - match TypeOptionCellExt::new_with_cell_data_cache(field, None) - .get_type_option_cell_data_handler(from_field_type) - { + match TypeOptionCellExt::new(field, None).get_type_option_cell_data_handler(from_field_type) { None => "".to_string(), Some(handler) => handler.handle_stringify_cell(cell, to_field_type, field), } diff --git a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs index 7e79d74291..0e8b90fbbc 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database/database_editor.rs @@ -4,7 +4,9 @@ use std::sync::Arc; use collab_database::database::MutexDatabase; use collab_database::fields::{Field, TypeOptionData}; use collab_database::rows::{Cell, Cells, CreateRowParams, Row, RowCell, RowDetail, RowId}; -use collab_database::views::{DatabaseLayout, DatabaseView, LayoutSetting, OrderObjectPosition}; +use collab_database::views::{ + DatabaseLayout, DatabaseView, FilterMap, LayoutSetting, OrderObjectPosition, +}; use futures::StreamExt; use lib_infra::box_any::BoxAny; use tokio::sync::{broadcast, RwLock}; @@ -32,7 +34,7 @@ use crate::services::field::{ use crate::services::field_settings::{ default_field_settings_by_layout_map, FieldSettings, FieldSettingsChangesetParams, }; -use crate::services::filter::Filter; +use crate::services::filter::{Filter, FilterChangeset}; use crate::services::group::{default_group_setting, GroupChangesets, GroupSetting, RowChangeset}; use crate::services::share::csv::{CSVExport, CSVFormat}; use crate::services::sort::Sort; @@ -214,16 +216,13 @@ impl DatabaseEditor { Ok(()) } - #[tracing::instrument(level = "trace", skip_all, err)] - pub async fn create_or_update_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> { - let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?; - view_editor.v_insert_filter(params).await?; - Ok(()) - } - - pub async fn delete_filter(&self, params: DeleteFilterPayloadPB) -> FlowyResult<()> { - let view_editor = self.database_views.get_view_editor(¶ms.view_id).await?; - view_editor.v_delete_filter(params).await?; + pub async fn modify_view_filters( + &self, + view_id: &str, + changeset: FilterChangeset, + ) -> FlowyResult<()> { + let view_editor = self.database_views.get_view_editor(view_id).await?; + view_editor.v_modify_filters(changeset).await?; Ok(()) } @@ -267,7 +266,8 @@ impl DatabaseEditor { pub async fn get_all_filters(&self, view_id: &str) -> RepeatedFilterPB { if let Ok(view_editor) = self.database_views.get_view_editor(view_id).await { - view_editor.v_get_all_filters().await.into() + let filters = view_editor.v_get_all_filters().await; + RepeatedFilterPB::from(&filters) } else { RepeatedFilterPB { items: vec![] } } @@ -1259,10 +1259,9 @@ impl DatabaseEditor { row_ids: Option<&Vec>, ) -> FlowyResult> { let primary_field = self.database.lock().fields.get_primary_field().unwrap(); - let handler = - TypeOptionCellExt::new_with_cell_data_cache(&primary_field, Some(self.cell_cache.clone())) - .get_type_option_cell_data_handler(&FieldType::RichText) - .ok_or(FlowyError::internal())?; + let handler = TypeOptionCellExt::new(&primary_field, Some(self.cell_cache.clone())) + .get_type_option_cell_data_handler(&FieldType::RichText) + .ok_or(FlowyError::internal())?; let row_data = { let database = self.database.lock(); @@ -1566,13 +1565,12 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { .get_calculation::(view_id, field_id) } - fn get_all_filters(&self, view_id: &str) -> Vec> { + fn get_all_filters(&self, view_id: &str) -> Vec { self .database .lock() .get_all_filters(view_id) .into_iter() - .map(Arc::new) .collect() } @@ -1581,7 +1579,14 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { } fn insert_filter(&self, view_id: &str, filter: Filter) { - self.database.lock().insert_filter(view_id, filter); + self.database.lock().insert_filter(view_id, &filter); + } + + fn save_filters(&self, view_id: &str, filters: &[Filter]) { + self + .database + .lock() + .save_filters::(view_id, filters); } fn get_filter(&self, view_id: &str, filter_id: &str) -> Option { @@ -1591,15 +1596,6 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { .get_filter::(view_id, filter_id) } - fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option { - self - .database - .lock() - .get_all_filters::(view_id) - .into_iter() - .find(|filter| filter.field_id == field_id) - } - fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option { self.database.lock().get_layout_setting(view_id, layout_ty) } @@ -1632,7 +1628,7 @@ impl DatabaseViewOperation for DatabaseViewOperationImpl { field: &Field, field_type: &FieldType, ) -> Option> { - TypeOptionCellExt::new_with_cell_data_cache(field, Some(self.cell_cache.clone())) + TypeOptionCellExt::new(field, Some(self.cell_cache.clone())) .get_type_option_cell_data_handler(field_type) } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs index 61ee522a0e..7b86a960ea 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_editor.rs @@ -2,9 +2,7 @@ use std::borrow::Cow; use std::collections::HashMap; use std::sync::Arc; -use collab_database::database::{ - gen_database_calculation_id, gen_database_filter_id, gen_database_sort_id, -}; +use collab_database::database::{gen_database_calculation_id, gen_database_sort_id}; use collab_database::fields::{Field, TypeOptionData}; use collab_database::rows::{Cells, Row, RowDetail, RowId}; use collab_database::views::{DatabaseLayout, DatabaseView}; @@ -15,11 +13,10 @@ use flowy_error::{FlowyError, FlowyResult}; use lib_dispatch::prelude::af_spawn; use crate::entities::{ - CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteFilterPayloadPB, - DeleteSortPayloadPB, FieldType, FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB, - LayoutSettingChangeset, LayoutSettingParams, RemoveCalculationChangesetPB, ReorderSortPayloadPB, - RowMetaPB, RowsChangePB, SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, - UpdateFilterParams, UpdateSortPayloadPB, + CalendarEventPB, DatabaseLayoutMetaPB, DatabaseLayoutSettingPB, DeleteSortPayloadPB, FieldType, + FieldVisibility, GroupChangesPB, GroupPB, InsertedRowPB, LayoutSettingChangeset, + LayoutSettingParams, RemoveCalculationChangesetPB, ReorderSortPayloadPB, RowMetaPB, RowsChangePB, + SortChangesetNotificationPB, SortPB, UpdateCalculationChangesetPB, UpdateSortPayloadPB, }; use crate::notification::{send_notification, DatabaseNotification}; use crate::services::calculations::{Calculation, CalculationChangeset, CalculationsController}; @@ -37,9 +34,7 @@ use crate::services::database_view::{ DatabaseViewChangedNotifier, DatabaseViewChangedReceiverRunner, }; use crate::services::field_settings::FieldSettings; -use crate::services::filter::{ - Filter, FilterChangeset, FilterContext, FilterController, UpdatedFilter, -}; +use crate::services::filter::{Filter, FilterChangeset, FilterController}; use crate::services::group::{GroupChangesets, GroupController, MoveGroupRowContext, RowChangeset}; use crate::services::setting::CalendarLayoutSetting; use crate::services::sort::{Sort, SortChangeset, SortController}; @@ -618,73 +613,28 @@ impl DatabaseViewEditor { Ok(()) } - pub async fn v_get_all_filters(&self) -> Vec> { + pub async fn v_get_all_filters(&self) -> Vec { self.delegate.get_all_filters(&self.view_id) } - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn v_insert_filter(&self, params: UpdateFilterParams) -> FlowyResult<()> { - let is_exist = params.filter_id.is_some(); - let filter_id = match params.filter_id { - None => gen_database_filter_id(), - Some(filter_id) => filter_id, - }; - let filter = Filter { - id: filter_id.clone(), - field_id: params.field_id.clone(), - field_type: params.field_type, - condition: params.condition, - content: params.content, - }; - let filter_controller = self.filter_controller.clone(); - let changeset = if is_exist { - let old_filter = self.delegate.get_filter(&self.view_id, &filter.id); - - self.delegate.insert_filter(&self.view_id, filter.clone()); - filter_controller - .did_receive_changes(FilterChangeset::from_update(UpdatedFilter::new( - old_filter, filter, - ))) - .await - } else { - self.delegate.insert_filter(&self.view_id, filter.clone()); - filter_controller - .did_receive_changes(FilterChangeset::from_insert(filter)) - .await - }; - drop(filter_controller); - - if let Some(changeset) = changeset { - notify_did_update_filter(changeset).await; - } - Ok(()) - } - - #[tracing::instrument(level = "trace", skip(self), err)] - pub async fn v_delete_filter(&self, params: DeleteFilterPayloadPB) -> FlowyResult<()> { - let filter_context = FilterContext { - filter_id: params.filter_id.clone(), - field_id: params.field_id.clone(), - field_type: params.field_type, - }; - let changeset = self - .filter_controller - .did_receive_changes(FilterChangeset::from_delete(filter_context.clone())) - .await; - - self - .delegate - .delete_filter(&self.view_id, ¶ms.filter_id); - if changeset.is_some() { - notify_did_update_filter(changeset.unwrap()).await; - } - Ok(()) - } - pub async fn v_get_filter(&self, filter_id: &str) -> Option { self.delegate.get_filter(&self.view_id, filter_id) } + #[tracing::instrument(level = "trace", skip(self), err)] + pub async fn v_modify_filters(&self, changeset: FilterChangeset) -> FlowyResult<()> { + let filter_controller = self.filter_controller.clone(); + + // self.delegate.insert_filter(&self.view_id, filter.clone()); + + let notification = filter_controller.apply_changeset(changeset).await; + + drop(filter_controller); + + notify_did_update_filter(notification).await; + Ok(()) + } + /// Returns the current calendar settings #[tracing::instrument(level = "trace", skip(self))] pub async fn v_get_layout_settings(&self, layout_ty: &DatabaseLayout) -> LayoutSettingParams { @@ -830,25 +780,13 @@ impl DatabaseViewEditor { }) .await; - if let Some(filter) = self - .delegate - .get_filter_by_field_id(&self.view_id, field_id) - { - let old = Filter { - field_type: FieldType::from(old_field.field_type), - ..filter.clone() - }; - let updated_filter = UpdatedFilter::new(Some(old), filter); - let filter_changeset = FilterChangeset::from_update(updated_filter); + if old_field.field_type != field.field_type { let filter_controller = self.filter_controller.clone(); - af_spawn(async move { - if let Some(notification) = filter_controller - .did_receive_changes(filter_changeset) - .await - { - notify_did_update_filter(notification).await; - } - }); + let changeset = FilterChangeset::DeleteAllWithFieldId { + field_id: field.id.clone(), + }; + let notification = filter_controller.apply_changeset(changeset).await; + notify_did_update_filter(notification).await; } } Ok(()) diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_filter.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_filter.rs index 75f31212d9..7944cacf4e 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_filter.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_filter.rs @@ -3,7 +3,7 @@ use std::sync::Arc; use collab_database::fields::Field; use collab_database::rows::{RowDetail, RowId}; -use lib_infra::future::{to_fut, Fut}; +use lib_infra::future::Fut; use crate::services::cell::CellCache; use crate::services::database_view::{ @@ -46,11 +46,6 @@ pub async fn make_filter_controller( struct DatabaseViewFilterDelegateImpl(Arc); impl FilterDelegate for DatabaseViewFilterDelegateImpl { - fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut>> { - let filter = self.0.get_filter(view_id, filter_id).map(Arc::new); - to_fut(async move { filter }) - } - fn get_field(&self, field_id: &str) -> Option { self.0.get_field(field_id) } @@ -66,4 +61,8 @@ impl FilterDelegate for DatabaseViewFilterDelegateImpl { fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut)>> { self.0.get_row(view_id, rows_id) } + + fn save_filters(&self, view_id: &str, filters: &[Filter]) { + self.0.save_filters(view_id, filters) + } } diff --git a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs index 40ad9778b5..5779fd6faf 100644 --- a/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs +++ b/frontend/rust-lib/flowy-database2/src/services/database_view/view_operation.rs @@ -91,15 +91,15 @@ pub trait DatabaseViewOperation: Send + Sync + 'static { fn remove_calculation(&self, view_id: &str, calculation_id: &str); - fn get_all_filters(&self, view_id: &str) -> Vec>; + fn get_all_filters(&self, view_id: &str) -> Vec; + + fn get_filter(&self, view_id: &str, filter_id: &str) -> Option; fn delete_filter(&self, view_id: &str, filter_id: &str); fn insert_filter(&self, view_id: &str, filter: Filter); - fn get_filter(&self, view_id: &str, filter_id: &str) -> Option; - - fn get_filter_by_field_id(&self, view_id: &str, field_id: &str) -> Option; + fn save_filters(&self, view_id: &str, filters: &[Filter]); fn get_layout_setting(&self, view_id: &str, layout_ty: &DatabaseLayout) -> Option; diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs index 01eb06835f..f3def99718 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/timestamp_type_option/timestamp_type_option.rs @@ -177,7 +177,7 @@ impl TypeOptionCellDataFilter for TimestampTypeOption { _filter: &::CellFilter, _cell_data: &::CellData, ) -> bool { - false + true } } diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs index 172b192728..905919015b 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option.rs @@ -19,7 +19,7 @@ use crate::services::field::{ CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption, RichTextTypeOption, SingleSelectTypeOption, TimestampTypeOption, URLTypeOption, }; -use crate::services::filter::FromFilterString; +use crate::services::filter::ParseFilterData; use crate::services::sort::SortCondition; pub trait TypeOption { @@ -58,7 +58,7 @@ pub trait TypeOption { type CellProtobufType: TryInto + Debug; /// Represents the filter configuration for this type option. - type CellFilter: FromFilterString + Send + Sync + 'static; + type CellFilter: ParseFilterData + Clone + Send + Sync + 'static; } /// This trait providing serialization and deserialization methods for cell data. /// diff --git a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs index 5492d92194..476aa3fd6c 100644 --- a/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs +++ b/frontend/rust-lib/flowy-database2/src/services/field/type_options/type_option_cell.rs @@ -9,9 +9,7 @@ use flowy_error::FlowyResult; use lib_infra::box_any::BoxAny; use crate::entities::FieldType; -use crate::services::cell::{ - CellCache, CellDataChangeset, CellDataDecoder, CellFilterCache, CellProtobufBlob, -}; +use crate::services::cell::{CellCache, CellDataChangeset, CellDataDecoder, CellProtobufBlob}; use crate::services::field::checklist_type_option::ChecklistTypeOption; use crate::services::field::{ CheckboxTypeOption, DateTypeOption, MultiSelectTypeOption, NumberTypeOption, RelationTypeOption, @@ -55,7 +53,7 @@ pub trait TypeOptionCellDataHandler: Send + Sync + 'static { sort_condition: SortCondition, ) -> Ordering; - fn handle_cell_filter(&self, field_type: &FieldType, field: &Field, cell: &Cell) -> bool; + fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool; /// Format the cell to string using the passed-in [FieldType] and [Field]. /// The [Cell] is generic, so we need to know the [FieldType] and [Field] to format the cell. @@ -100,7 +98,6 @@ impl AsRef for CellDataCacheKey { struct TypeOptionCellDataHandlerImpl { inner: T, cell_data_cache: Option, - cell_filter_cache: Option, } impl TypeOptionCellDataHandlerImpl @@ -122,13 +119,11 @@ where pub fn new_with_boxed( inner: T, - cell_filter_cache: Option, cell_data_cache: Option, ) -> Box { Self { inner, cell_data_cache, - cell_filter_cache, } .into_boxed() } @@ -308,11 +303,11 @@ where } } - fn handle_cell_filter(&self, field_type: &FieldType, field: &Field, cell: &Cell) -> bool { + fn handle_cell_filter(&self, field: &Field, cell: &Cell, filter: &BoxAny) -> bool { let perform_filter = || { - let filter_cache = self.cell_filter_cache.as_ref()?.read(); - let cell_filter = filter_cache.get::<::CellFilter>(&field.id)?; - let cell_data = self.get_decoded_cell_data(cell, field_type, field).ok()?; + let field_type = FieldType::from(field.field_type); + let cell_filter = filter.downcast_ref::<::CellFilter>()?; + let cell_data = self.get_decoded_cell_data(cell, &field_type, field).ok()?; Some(self.apply_filter(cell_filter, &cell_data)) }; @@ -362,28 +357,16 @@ where pub struct TypeOptionCellExt<'a> { field: &'a Field, cell_data_cache: Option, - cell_filter_cache: Option, } impl<'a> TypeOptionCellExt<'a> { - pub fn new_with_cell_data_cache(field: &'a Field, cell_data_cache: Option) -> Self { + pub fn new(field: &'a Field, cell_data_cache: Option) -> Self { Self { field, cell_data_cache, - cell_filter_cache: None, } } - pub fn new( - field: &'a Field, - cell_data_cache: Option, - cell_filter_cache: Option, - ) -> Self { - let mut this = Self::new_with_cell_data_cache(field, cell_data_cache); - this.cell_filter_cache = cell_filter_cache; - this - } - pub fn get_cells(&self) -> Vec { let field_type = FieldType::from(self.field.field_type); match self.get_type_option_cell_data_handler(&field_type) { @@ -403,103 +386,63 @@ impl<'a> TypeOptionCellExt<'a> { .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::Number => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::DateTime => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::LastEditedTime | FieldType::CreatedTime => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::SingleSelect => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::MultiSelect => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::Checkbox => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::URL => { self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }) }, FieldType::Checklist => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), FieldType::Relation => self .field .get_type_option::(field_type) .map(|type_option| { - TypeOptionCellDataHandlerImpl::new_with_boxed( - type_option, - self.cell_filter_cache.clone(), - self.cell_data_cache.clone(), - ) + TypeOptionCellDataHandlerImpl::new_with_boxed(type_option, self.cell_data_cache.clone()) }), } } diff --git a/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs b/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs index 122b714ee6..11b8224c36 100644 --- a/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/filter/controller.rs @@ -2,8 +2,9 @@ use std::collections::HashMap; use std::str::FromStr; use std::sync::Arc; +use collab_database::database::gen_database_filter_id; use collab_database::fields::Field; -use collab_database::rows::{Cell, Row, RowDetail, RowId}; +use collab_database::rows::{Row, RowDetail, RowId}; use dashmap::DashMap; use serde::{Deserialize, Serialize}; use tokio::sync::RwLock; @@ -14,33 +15,26 @@ use lib_infra::priority_task::{QualityOfService, Task, TaskContent, TaskDispatch use crate::entities::filter_entities::*; use crate::entities::{FieldType, InsertedRowPB, RowMetaPB}; -use crate::services::cell::{CellCache, CellFilterCache}; +use crate::services::cell::CellCache; use crate::services::database_view::{DatabaseViewChanged, DatabaseViewChangedNotifier}; -use crate::services::field::*; -use crate::services::filter::{Filter, FilterChangeset, FilterResult, FilterResultNotification}; -use crate::utils::cache::AnyTypeCache; +use crate::services::field::TypeOptionCellExt; +use crate::services::filter::{Filter, FilterChangeset, FilterInner, FilterResultNotification}; pub trait FilterDelegate: Send + Sync + 'static { - fn get_filter(&self, view_id: &str, filter_id: &str) -> Fut>>; fn get_field(&self, field_id: &str) -> Option; fn get_fields(&self, view_id: &str, field_ids: Option>) -> Fut>>; fn get_rows(&self, view_id: &str) -> Fut>>; fn get_row(&self, view_id: &str, rows_id: &RowId) -> Fut)>>; -} - -pub trait FromFilterString { - fn from_filter(filter: &Filter) -> Self - where - Self: Sized; + fn save_filters(&self, view_id: &str, filters: &[Filter]); } pub struct FilterController { view_id: String, handler_id: String, delegate: Box, - result_by_row_id: DashMap, + result_by_row_id: DashMap, cell_cache: CellCache, - cell_filter_cache: CellFilterCache, + filters: RwLock>, task_scheduler: Arc>, notifier: DatabaseViewChangedNotifier, } @@ -57,26 +51,23 @@ impl FilterController { handler_id: &str, delegate: T, task_scheduler: Arc>, - filters: Vec>, + filters: Vec, cell_cache: CellCache, notifier: DatabaseViewChangedNotifier, ) -> Self where T: FilterDelegate + 'static, { - let this = Self { + Self { view_id: view_id.to_string(), handler_id: handler_id.to_string(), delegate: Box::new(delegate), result_by_row_id: DashMap::default(), cell_cache, - // Cache by field_id - cell_filter_cache: AnyTypeCache::::new(), + filters: RwLock::new(filters), task_scheduler, notifier, - }; - this.refresh_filters(filters).await; - this + } } pub async fn close(&self) { @@ -100,7 +91,9 @@ impl FilterController { } pub async fn filter_rows(&self, rows: &mut Vec>) { - if self.cell_filter_cache.read().is_empty() { + let filters = self.filters.read().await; + + if filters.is_empty() { return; } let field_by_field_id = self.get_field_map().await; @@ -110,7 +103,7 @@ impl FilterController { &self.result_by_row_id, &field_by_field_id, &self.cell_cache, - &self.cell_filter_cache, + &filters, ); }); @@ -118,7 +111,7 @@ impl FilterController { self .result_by_row_id .get(&row_detail.row.id) - .map(|result| result.is_visible()) + .map(|result| *result) .unwrap_or(false) }); } @@ -144,12 +137,14 @@ impl FilterController { let event_type = FilterEvent::from_str(predicate).unwrap(); match event_type { FilterEvent::FilterDidChanged => self.filter_all_rows().await?, - FilterEvent::RowDidChanged(row_id) => self.filter_row(row_id).await?, + FilterEvent::RowDidChanged(row_id) => self.filter_single_row(row_id).await?, } Ok(()) } - async fn filter_row(&self, row_id: RowId) -> FlowyResult<()> { + async fn filter_single_row(&self, row_id: RowId) -> FlowyResult<()> { + let filters = self.filters.read().await; + if let Some((_, row_detail)) = self.delegate.get_row(&self.view_id, &row_id).await { let field_by_field_id = self.get_field_map().await; let mut notification = FilterResultNotification::new(self.view_id.clone()); @@ -158,7 +153,7 @@ impl FilterController { &self.result_by_row_id, &field_by_field_id, &self.cell_cache, - &self.cell_filter_cache, + &filters, ) { if is_visible { if let Some((index, _row)) = self.delegate.get_row(&self.view_id, &row_id).await { @@ -179,6 +174,8 @@ impl FilterController { } async fn filter_all_rows(&self) -> FlowyResult<()> { + let filters = self.filters.read().await; + let field_by_field_id = self.get_field_map().await; let mut visible_rows = vec![]; let mut invisible_rows = vec![]; @@ -195,7 +192,7 @@ impl FilterController { &self.result_by_row_id, &field_by_field_id, &self.cell_cache, - &self.cell_filter_cache, + &filters, ) { if is_visible { let row_meta = RowMetaPB::from(row_detail.as_ref()); @@ -211,15 +208,16 @@ impl FilterController { invisible_rows, visible_rows, }; - tracing::Span::current().record("filter_result", format!("{:?}", ¬ification).as_str()); + tracing::trace!("filter result {:?}", filters); let _ = self .notifier .send(DatabaseViewChanged::FilterNotification(notification)); + Ok(()) } pub async fn did_receive_row_changed(&self, row_id: RowId) { - if !self.cell_filter_cache.read().is_empty() { + if !self.filters.read().await.is_empty() { self .gen_task( FilterEvent::RowDidChanged(row_id), @@ -230,206 +228,196 @@ impl FilterController { } #[tracing::instrument(level = "trace", skip(self))] - pub async fn did_receive_changes( - &self, - changeset: FilterChangeset, - ) -> Option { - let mut notification: Option = None; + pub async fn apply_changeset(&self, changeset: FilterChangeset) -> FilterChangesetNotificationPB { + let mut filters = self.filters.write().await; - if let Some(filter_type) = &changeset.insert_filter { - if let Some(filter) = self.filter_from_filter_id(&filter_type.id).await { - notification = Some(FilterChangesetNotificationPB::from_insert( - &self.view_id, - vec![filter], - )); - } - if let Some(filter) = self - .delegate - .get_filter(&self.view_id, &filter_type.id) - .await - { - self.refresh_filters(vec![filter]).await; - } - } - - if let Some(updated_filter_type) = changeset.update_filter { - if let Some(old_filter_type) = updated_filter_type.old { - let new_filter = self - .filter_from_filter_id(&updated_filter_type.new.id) - .await; - let old_filter = self.filter_from_filter_id(&old_filter_type.id).await; - - // Get the filter id - let mut filter_id = old_filter.map(|filter| filter.id); - if filter_id.is_none() { - filter_id = new_filter.as_ref().map(|filter| filter.id.clone()); + match changeset { + FilterChangeset::Insert { + parent_filter_id, + data, + } => { + let new_filter = Filter { + id: gen_database_filter_id(), + inner: data, + }; + match parent_filter_id { + Some(parent_filter_id) => { + if let Some(parent_filter) = filters + .iter_mut() + .find_map(|filter| filter.find_filter(&parent_filter_id)) + { + // TODO(RS): error handling for inserting filters + let _result = parent_filter.insert_filter(new_filter); + } + }, + None => { + filters.push(new_filter); + }, } - - if let Some(filter_id) = filter_id { - // Update the corresponding filter in the cache - if let Some(filter) = self.delegate.get_filter(&self.view_id, &filter_id).await { - self.refresh_filters(vec![filter]).await; + }, + FilterChangeset::UpdateType { + filter_id, + filter_type, + } => { + for filter in filters.iter_mut() { + let filter = filter.find_filter(&filter_id); + if let Some(filter) = filter { + let result = filter.convert_to_and_or_filter_type(filter_type); + if result.is_ok() { + break; + } } - - notification = Some(FilterChangesetNotificationPB::from_update( - &self.view_id, - vec![UpdatedFilter { - filter_id, - filter: new_filter, - }], - )); } - } + }, + FilterChangeset::UpdateData { filter_id, data } => { + if let Some(filter) = filters + .iter_mut() + .find_map(|filter| filter.find_filter(&filter_id)) + { + // TODO(RS): error handling for updating filter data + let _result = filter.update_filter_data(data); + } + }, + FilterChangeset::Delete { + filter_id, + field_id: _, + } => { + for (position, filter) in filters.iter_mut().enumerate() { + if filter.id == filter_id { + filters.remove(position); + break; + } + let parent_filter = filter.find_parent_of_filter(&filter_id); + if let Some(filter) = parent_filter { + let result = filter.delete_filter(&filter_id); + if result.is_ok() { + break; + } + } + } + }, + FilterChangeset::DeleteAllWithFieldId { field_id } => { + let mut filter_ids: Vec = vec![]; + for filter in filters.iter_mut() { + filter.find_all_filters_with_field_id(&field_id, &mut filter_ids); + } + + for filter_id in filter_ids { + for (position, filter) in filters.iter_mut().enumerate() { + if filter.id == filter_id { + filters.remove(position); + break; + } + let parent_filter = filter.find_parent_of_filter(&filter_id); + if let Some(filter) = parent_filter { + let _ = filter.delete_filter(&filter_id); + } + } + } + }, } - if let Some(filter_context) = &changeset.delete_filter { - if let Some(filter) = self.filter_from_filter_id(&filter_context.filter_id).await { - notification = Some(FilterChangesetNotificationPB::from_delete( - &self.view_id, - vec![filter], - )); - } - self - .cell_filter_cache - .write() - .remove(&filter_context.field_id); - } + self.delegate.save_filters(&self.view_id, &filters); self .gen_task(FilterEvent::FilterDidChanged, QualityOfService::Background) .await; - tracing::trace!("{:?}", notification); - notification - } - async fn filter_from_filter_id(&self, filter_id: &str) -> Option { - self - .delegate - .get_filter(&self.view_id, filter_id) - .await - .map(|filter| FilterPB::from(filter.as_ref())) - } - - #[tracing::instrument(level = "trace", skip_all)] - async fn refresh_filters(&self, filters: Vec>) { - for filter in filters { - let field_id = &filter.field_id; - tracing::trace!("Create filter with type: {:?}", filter.field_type); - match &filter.field_type { - FieldType::RichText => { - self - .cell_filter_cache - .write() - .insert(field_id, TextFilterPB::from_filter(filter.as_ref())); - }, - FieldType::Number => { - self - .cell_filter_cache - .write() - .insert(field_id, NumberFilterPB::from_filter(filter.as_ref())); - }, - FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => { - self - .cell_filter_cache - .write() - .insert(field_id, DateFilterPB::from_filter(filter.as_ref())); - }, - FieldType::SingleSelect | FieldType::MultiSelect => { - self - .cell_filter_cache - .write() - .insert(field_id, SelectOptionFilterPB::from_filter(filter.as_ref())); - }, - FieldType::Checkbox => { - self - .cell_filter_cache - .write() - .insert(field_id, CheckboxFilterPB::from_filter(filter.as_ref())); - }, - FieldType::URL => { - self - .cell_filter_cache - .write() - .insert(field_id, TextFilterPB::from_filter(filter.as_ref())); - }, - FieldType::Checklist => { - self - .cell_filter_cache - .write() - .insert(field_id, ChecklistFilterPB::from_filter(filter.as_ref())); - }, - FieldType::Relation => { - self - .cell_filter_cache - .write() - .insert(field_id, RelationFilterPB::from_filter(filter.as_ref())); - }, - } - } + FilterChangesetNotificationPB::from_filters(&self.view_id, &filters) } } -/// Returns None if there is no change in this row after applying the filter +/// Returns `Some` if the visibility of the row changed after applying the filter and `None` +/// otherwise #[tracing::instrument(level = "trace", skip_all)] fn filter_row( row: &Row, - result_by_row_id: &DashMap, + result_by_row_id: &DashMap, field_by_field_id: &HashMap>, cell_data_cache: &CellCache, - cell_filter_cache: &CellFilterCache, + filters: &Vec, ) -> Option<(RowId, bool)> { - // Create a filter result cache if it's not exist - let mut filter_result = result_by_row_id.entry(row.id.clone()).or_default(); - let old_is_visible = filter_result.is_visible(); + // Create a filter result cache if it doesn't exist + let mut filter_result = result_by_row_id.entry(row.id.clone()).or_insert(true); + let old_is_visible = *filter_result; - // Iterate each cell of the row to check its visibility - for (field_id, field) in field_by_field_id { - if !cell_filter_cache.read().contains(field_id) { - filter_result.visible_by_field_id.remove(field_id); - continue; - } - - let cell = row.cells.get(field_id).cloned(); - let field_type = FieldType::from(field.field_type); - // if the visibility of the cell_rew is changed, which means the visibility of the - // row is changed too. - if let Some(is_visible) = - filter_cell(&field_type, field, cell, cell_data_cache, cell_filter_cache) - { - filter_result - .visible_by_field_id - .insert(field_id.to_string(), is_visible); + let mut new_is_visible = true; + for filter in filters { + if let Some(is_visible) = apply_filter(row, field_by_field_id, cell_data_cache, filter) { + new_is_visible = new_is_visible && is_visible; } } - let is_visible = filter_result.is_visible(); - if old_is_visible != is_visible { - Some((row.id.clone(), is_visible)) + *filter_result = new_is_visible; + + if old_is_visible != new_is_visible { + Some((row.id.clone(), new_is_visible)) } else { None } } -// Returns None if there is no change in this cell after applying the filter -// Returns Some if the visibility of the cell is changed - -#[tracing::instrument(level = "trace", skip_all, fields(cell_content))] -fn filter_cell( - field_type: &FieldType, - field: &Arc, - cell: Option, +/// Recursively applies a `Filter` to a `Row`'s cells. +fn apply_filter( + row: &Row, + field_by_field_id: &HashMap>, cell_data_cache: &CellCache, - cell_filter_cache: &CellFilterCache, + filter: &Filter, ) -> Option { - let handler = TypeOptionCellExt::new( - field.as_ref(), - Some(cell_data_cache.clone()), - Some(cell_filter_cache.clone()), - ) - .get_type_option_cell_data_handler(field_type)?; - let is_visible = - handler.handle_cell_filter(field_type, field.as_ref(), &cell.unwrap_or_default()); - Some(is_visible) + match &filter.inner { + FilterInner::And { children } => { + if children.is_empty() { + return None; + } + for child_filter in children.iter() { + if let Some(false) = apply_filter(row, field_by_field_id, cell_data_cache, child_filter) { + return Some(false); + } + } + Some(true) + }, + FilterInner::Or { children } => { + if children.is_empty() { + return None; + } + for child_filter in children.iter() { + if let Some(true) = apply_filter(row, field_by_field_id, cell_data_cache, child_filter) { + return Some(true); + } + } + Some(false) + }, + FilterInner::Data { + field_id, + field_type, + condition_and_content, + } => { + let field = match field_by_field_id.get(field_id) { + Some(field) => field, + None => { + tracing::error!("cannot find field"); + return Some(false); + }, + }; + if *field_type != FieldType::from(field.field_type) { + tracing::error!("field type of filter doesn't match field type of field"); + return Some(false); + } + let cell = row.cells.get(field_id).cloned(); + let field_type = FieldType::from(field.field_type); + if let Some(handler) = TypeOptionCellExt::new(field.as_ref(), Some(cell_data_cache.clone())) + .get_type_option_cell_data_handler(&field_type) + { + Some(handler.handle_cell_filter( + field.as_ref(), + &cell.unwrap_or_default(), + condition_and_content, + )) + } else { + Some(true) + } + }, + } } #[derive(Serialize, Deserialize, Clone, Debug)] @@ -446,6 +434,7 @@ impl ToString for FilterEvent { impl FromStr for FilterEvent { type Err = serde_json::Error; + fn from_str(s: &str) -> Result { serde_json::from_str(s) } diff --git a/frontend/rust-lib/flowy-database2/src/services/filter/entities.rs b/frontend/rust-lib/flowy-database2/src/services/filter/entities.rs index 27e220b0c2..980b69a7aa 100644 --- a/frontend/rust-lib/flowy-database2/src/services/filter/entities.rs +++ b/frontend/rust-lib/flowy-database2/src/services/filter/entities.rs @@ -1,125 +1,397 @@ +use std::mem; + use anyhow::bail; use collab::core::any_map::AnyMapExtension; +use collab_database::database::gen_database_filter_id; use collab_database::rows::RowId; use collab_database::views::{FilterMap, FilterMapBuilder}; +use flowy_error::{FlowyError, FlowyResult}; +use lib_infra::box_any::BoxAny; -use crate::entities::{FieldType, FilterPB, InsertedRowPB}; +use crate::entities::{ + CheckboxFilterPB, ChecklistFilterPB, DateFilterContentPB, DateFilterPB, FieldType, FilterType, + InsertedRowPB, NumberFilterPB, RelationFilterPB, SelectOptionFilterPB, TextFilterPB, +}; +use crate::services::field::SelectOptionIds; -#[derive(Debug, Clone)] +pub trait ParseFilterData { + fn parse(condition: u8, content: String) -> Self; +} + +#[derive(Debug)] pub struct Filter { pub id: String, - pub field_id: String, - pub field_type: FieldType, - pub condition: i64, - pub content: String, + pub inner: FilterInner, +} + +impl Filter { + /// Recursively determine whether there are any data filters in the filter tree. + pub fn is_empty(&self) -> bool { + match &self.inner { + FilterInner::And { children } | FilterInner::Or { children } => children + .iter() + .map(|filter| filter.is_empty()) + .all(|is_empty| is_empty), + FilterInner::Data { .. } => false, + } + } + + pub fn find_filter(&mut self, filter_id: &str) -> Option<&mut Self> { + if self.id == filter_id { + return Some(self); + } + match &mut self.inner { + FilterInner::And { children } | FilterInner::Or { children } => { + for child_filter in children.iter_mut() { + let result = child_filter.find_filter(filter_id); + if result.is_some() { + return result; + } + } + None + }, + FilterInner::Data { .. } => None, + } + } + + pub fn find_parent_of_filter(&mut self, filter_id: &str) -> Option<&mut Self> { + if self.id == filter_id { + return None; + } + match &mut self.inner { + FilterInner::And { children } | FilterInner::Or { children } => { + for child_filter in children.iter_mut() { + if child_filter.id == filter_id { + return Some(child_filter); + } + let result = child_filter.find_parent_of_filter(filter_id); + if result.is_some() { + return result; + } + } + None + }, + FilterInner::Data { .. } => None, + } + } + + /// converts a filter from And/Or/Data to And/Or. If the current type of the filter is Data, + /// return the FilterInner after the conversion. + pub fn convert_to_and_or_filter_type( + &mut self, + filter_type: FilterType, + ) -> FlowyResult> { + match (&mut self.inner, filter_type) { + (FilterInner::And { children }, FilterType::Or) => { + self.inner = FilterInner::Or { + children: mem::take(children), + }; + Ok(None) + }, + (FilterInner::Or { children }, FilterType::And) => { + self.inner = FilterInner::And { + children: mem::take(children), + }; + Ok(None) + }, + (FilterInner::Data { .. }, FilterType::And) => { + let mut inner = FilterInner::And { children: vec![] }; + mem::swap(&mut self.inner, &mut inner); + Ok(Some(inner)) + }, + (FilterInner::Data { .. }, FilterType::Or) => { + let mut inner = FilterInner::Or { children: vec![] }; + mem::swap(&mut self.inner, &mut inner); + Ok(Some(inner)) + }, + (_, FilterType::Data) => { + // from And/Or to Data + Err(FlowyError::internal().with_context(format!( + "conversion from {:?} to FilterType::Data not supported", + FilterType::from(&self.inner) + ))) + }, + _ => { + tracing::warn!("conversion to the same filter type"); + Ok(None) + }, + } + } + + pub fn insert_filter(&mut self, filter: Filter) -> FlowyResult<()> { + match &mut self.inner { + FilterInner::And { children } | FilterInner::Or { children } => { + children.push(filter); + }, + FilterInner::Data { .. } => { + // convert to FilterInner::And by default + let old_filter = self + .convert_to_and_or_filter_type(FilterType::And) + .and_then(|result| { + result.ok_or_else(|| FlowyError::internal().with_context("failed to convert filter")) + })?; + self.insert_filter(Filter { + id: gen_database_filter_id(), + inner: old_filter, + })?; + self.insert_filter(filter)?; + }, + } + + Ok(()) + } + + pub fn update_filter_data(&mut self, filter_data: FilterInner) -> FlowyResult<()> { + match &self.inner { + FilterInner::And { .. } | FilterInner::Or { .. } => Err(FlowyError::internal().with_context( + format!("unexpected filter type {:?}", FilterType::from(&self.inner)), + )), + _ => { + self.inner = filter_data; + Ok(()) + }, + } + } + + pub fn delete_filter(&mut self, filter_id: &str) -> FlowyResult<()> { + match &mut self.inner { + FilterInner::And { children } | FilterInner::Or { children } => children + .iter() + .position(|filter| filter.id == filter_id) + .map(|position| { + children.remove(position); + }) + .ok_or_else(|| { + FlowyError::internal() + .with_context(format!("filter with filter_id {:?} not found", filter_id)) + }), + FilterInner::Data { .. } => Err( + FlowyError::internal().with_context("unexpected parent filter type of FilterInner::Data"), + ), + } + } + + pub fn find_all_filters_with_field_id(&mut self, matching_field_id: &str, ids: &mut Vec) { + match &mut self.inner { + FilterInner::And { children } | FilterInner::Or { children } => { + for child_filter in children.iter_mut() { + child_filter.find_all_filters_with_field_id(matching_field_id, ids); + } + }, + FilterInner::Data { + field_id, + field_type: _, + condition_and_content: _, + } => { + if field_id == matching_field_id { + ids.push(self.id.clone()); + } + }, + } + } +} + +#[derive(Debug)] +pub enum FilterInner { + And { + children: Vec, + }, + Or { + children: Vec, + }, + Data { + field_id: String, + field_type: FieldType, + condition_and_content: BoxAny, + }, +} + +impl FilterInner { + pub fn new_data( + field_id: String, + field_type: FieldType, + condition: i64, + content: String, + ) -> Self { + let condition_and_content = match field_type { + FieldType::RichText | FieldType::URL => { + BoxAny::new(TextFilterPB::parse(condition as u8, content)) + }, + FieldType::Number => BoxAny::new(NumberFilterPB::parse(condition as u8, content)), + FieldType::DateTime | FieldType::CreatedTime | FieldType::LastEditedTime => { + BoxAny::new(DateFilterPB::parse(condition as u8, content)) + }, + FieldType::SingleSelect | FieldType::MultiSelect => { + BoxAny::new(SelectOptionFilterPB::parse(condition as u8, content)) + }, + FieldType::Checklist => BoxAny::new(ChecklistFilterPB::parse(condition as u8, content)), + FieldType::Checkbox => BoxAny::new(CheckboxFilterPB::parse(condition as u8, content)), + FieldType::Relation => BoxAny::new(RelationFilterPB::parse(condition as u8, content)), + }; + + FilterInner::Data { + field_id, + field_type, + condition_and_content, + } + } + + pub fn get_int_repr(&self) -> i64 { + match self { + FilterInner::And { .. } => FILTER_AND_INDEX, + FilterInner::Or { .. } => FILTER_OR_INDEX, + FilterInner::Data { .. } => FILTER_DATA_INDEX, + } + } } const FILTER_ID: &str = "id"; +const FILTER_TYPE: &str = "filter_type"; const FIELD_ID: &str = "field_id"; const FIELD_TYPE: &str = "ty"; const FILTER_CONDITION: &str = "condition"; const FILTER_CONTENT: &str = "content"; +const FILTER_CHILDREN: &str = "children"; -impl From for FilterMap { - fn from(data: Filter) -> Self { - FilterMapBuilder::new() - .insert_str_value(FILTER_ID, data.id) - .insert_str_value(FIELD_ID, data.field_id) - .insert_str_value(FILTER_CONTENT, data.content) - .insert_i64_value(FIELD_TYPE, data.field_type.into()) - .insert_i64_value(FILTER_CONDITION, data.condition) - .build() +const FILTER_AND_INDEX: i64 = 0; +const FILTER_OR_INDEX: i64 = 1; +const FILTER_DATA_INDEX: i64 = 2; + +impl<'a> From<&'a Filter> for FilterMap { + fn from(filter: &'a Filter) -> Self { + let mut builder = FilterMapBuilder::new() + .insert_str_value(FILTER_ID, &filter.id) + .insert_i64_value(FILTER_TYPE, filter.inner.get_int_repr()); + + builder = match &filter.inner { + FilterInner::And { children } | FilterInner::Or { children } => { + builder.insert_maps(FILTER_CHILDREN, children.iter().collect::>()) + }, + FilterInner::Data { + field_id, + field_type, + condition_and_content, + } => { + let get_raw_condition_and_content = || -> Option<(u8, String)> { + let (condition, content) = match field_type { + FieldType::RichText | FieldType::URL => { + let filter = condition_and_content.cloned::()?; + (filter.condition as u8, filter.content) + }, + FieldType::Number => { + let filter = condition_and_content.cloned::()?; + (filter.condition as u8, filter.content) + }, + FieldType::DateTime | FieldType::LastEditedTime | FieldType::CreatedTime => { + let filter = condition_and_content.cloned::()?; + let content = DateFilterContentPB { + start: filter.start, + end: filter.end, + timestamp: filter.timestamp, + } + .to_string(); + (filter.condition as u8, content) + }, + FieldType::SingleSelect | FieldType::MultiSelect => { + let filter = condition_and_content.cloned::()?; + let content = SelectOptionIds::from(filter.option_ids).to_string(); + (filter.condition as u8, content) + }, + FieldType::Checkbox => { + let filter = condition_and_content.cloned::()?; + (filter.condition as u8, "".to_string()) + }, + FieldType::Checklist => { + let filter = condition_and_content.cloned::()?; + (filter.condition as u8, "".to_string()) + }, + FieldType::Relation => { + let filter = condition_and_content.cloned::()?; + (filter.condition as u8, "".to_string()) + }, + }; + Some((condition, content)) + }; + + let (condition, content) = get_raw_condition_and_content().unwrap_or_else(|| { + tracing::error!("cannot deserialize filter condition and content filter properly"); + Default::default() + }); + + builder + .insert_str_value(FIELD_ID, field_id) + .insert_i64_value(FIELD_TYPE, field_type.into()) + .insert_i64_value(FILTER_CONDITION, condition as i64) + .insert_str_value(FILTER_CONTENT, content) + }, + }; + + builder.build() } } impl TryFrom for Filter { type Error = anyhow::Error; - fn try_from(filter: FilterMap) -> Result { - match ( - filter.get_str_value(FILTER_ID), - filter.get_str_value(FIELD_ID), - ) { - (Some(id), Some(field_id)) => { - let condition = filter.get_i64_value(FILTER_CONDITION).unwrap_or(0); - let content = filter.get_str_value(FILTER_CONTENT).unwrap_or_default(); - let field_type = filter - .get_i64_value(FIELD_TYPE) - .map(FieldType::from) - .unwrap_or_default(); - Ok(Filter { - id, - field_id, - field_type, - condition, - content, - }) + fn try_from(filter_map: FilterMap) -> Result { + let filter_id = filter_map + .get_str_value(FILTER_ID) + .ok_or_else(|| anyhow::anyhow!("invalid filter data"))?; + let filter_type = filter_map + .get_i64_value(FILTER_TYPE) + .unwrap_or(FILTER_DATA_INDEX); + + let filter = Filter { + id: filter_id, + inner: match filter_type { + FILTER_AND_INDEX => FilterInner::And { + children: filter_map.try_get_array(FILTER_CHILDREN), + }, + FILTER_OR_INDEX => FilterInner::Or { + children: filter_map.try_get_array(FILTER_CHILDREN), + }, + FILTER_DATA_INDEX => { + let field_id = filter_map + .get_str_value(FIELD_ID) + .ok_or_else(|| anyhow::anyhow!("invalid filter data"))?; + let field_type = filter_map + .get_i64_value(FIELD_TYPE) + .map(FieldType::from) + .unwrap_or_default(); + let condition = filter_map.get_i64_value(FILTER_CONDITION).unwrap_or(0); + let content = filter_map.get_str_value(FILTER_CONTENT).unwrap_or_default(); + + FilterInner::new_data(field_id, field_type, condition, content) + }, + _ => bail!("Unsupported filter type"), }, - _ => { - bail!("Invalid filter data") - }, - } + }; + + Ok(filter) } } -#[derive(Debug)] -pub struct FilterChangeset { - pub(crate) insert_filter: Option, - pub(crate) update_filter: Option, - pub(crate) delete_filter: Option, -} #[derive(Debug)] -pub struct UpdatedFilter { - pub old: Option, - pub new: Filter, -} - -impl UpdatedFilter { - pub fn new(old: Option, new: Filter) -> UpdatedFilter { - Self { old, new } - } -} - -impl FilterChangeset { - pub fn from_insert(filter: Filter) -> Self { - Self { - insert_filter: Some(filter), - update_filter: None, - delete_filter: None, - } - } - - pub fn from_update(filter: UpdatedFilter) -> Self { - Self { - insert_filter: None, - update_filter: Some(filter), - delete_filter: None, - } - } - pub fn from_delete(filter_context: FilterContext) -> Self { - Self { - insert_filter: None, - update_filter: None, - delete_filter: Some(filter_context), - } - } -} - -#[derive(Debug, Clone)] -pub struct FilterContext { - pub filter_id: String, - pub field_id: String, - pub field_type: FieldType, -} - -impl From<&FilterPB> for FilterContext { - fn from(filter: &FilterPB) -> Self { - Self { - filter_id: filter.id.clone(), - field_id: filter.field_id.clone(), - field_type: filter.field_type, - } - } +pub enum FilterChangeset { + Insert { + parent_filter_id: Option, + data: FilterInner, + }, + UpdateType { + filter_id: String, + filter_type: FilterType, + }, + UpdateData { + filter_id: String, + data: FilterInner, + }, + Delete { + filter_id: String, + field_id: String, + }, + DeleteAllWithFieldId { + field_id: String, + }, } #[derive(Clone, Debug)] diff --git a/frontend/rust-lib/flowy-database2/src/services/filter/task.rs b/frontend/rust-lib/flowy-database2/src/services/filter/task.rs index dbf55776df..03ed453f89 100644 --- a/frontend/rust-lib/flowy-database2/src/services/filter/task.rs +++ b/frontend/rust-lib/flowy-database2/src/services/filter/task.rs @@ -1,7 +1,6 @@ use crate::services::filter::FilterController; use lib_infra::future::BoxResultFuture; use lib_infra::priority_task::{TaskContent, TaskHandler}; -use std::collections::HashMap; use std::sync::Arc; pub struct FilterTaskHandler { @@ -40,21 +39,3 @@ impl TaskHandler for FilterTaskHandler { }) } } -/// Refresh the filter according to the field id. -#[derive(Default)] -pub(crate) struct FilterResult { - pub(crate) visible_by_field_id: HashMap, -} - -impl FilterResult { - pub(crate) fn is_visible(&self) -> bool { - let mut is_visible = true; - for visible in self.visible_by_field_id.values() { - if !is_visible { - break; - } - is_visible = *visible; - } - is_visible - } -} diff --git a/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs b/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs index 0c968dd353..8ff25772e2 100644 --- a/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs +++ b/frontend/rust-lib/flowy-database2/src/services/sort/controller.rs @@ -340,7 +340,7 @@ fn cmp_cell( cell_data_cache: &CellCache, sort_condition: SortCondition, ) -> Ordering { - match TypeOptionCellExt::new_with_cell_data_cache(field.as_ref(), Some(cell_data_cache.clone())) + match TypeOptionCellExt::new(field.as_ref(), Some(cell_data_cache.clone())) .get_type_option_cell_data_handler(&field_type) { None => default_order(), diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs new file mode 100644 index 0000000000..107e588fed --- /dev/null +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/advanced_filter_test.rs @@ -0,0 +1,314 @@ +use bytes::Bytes; +use flowy_database2::entities::{ + CheckboxFilterConditionPB, CheckboxFilterPB, DateFilterConditionPB, DateFilterPB, FieldType, + FilterDataPB, FilterPB, FilterType, NumberFilterConditionPB, NumberFilterPB, +}; +use lib_infra::box_any::BoxAny; +use protobuf::ProtobufError; +use std::convert::TryInto; + +use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged, FilterScript::*}; + +/// Create a single advanced filter: +/// +/// 1. Add an OR filter +/// 2. Add a Checkbox and an AND filter to its children +/// 3. Add a DateTime and a Number filter to the AND filter's children +/// +#[tokio::test] +async fn create_advanced_filter_test() { + let mut test = DatabaseFilterTest::new().await; + + let create_checkbox_filter = || -> CheckboxFilterPB { + CheckboxFilterPB { + condition: CheckboxFilterConditionPB::IsChecked, + } + }; + + let create_date_filter = || -> DateFilterPB { + DateFilterPB { + condition: DateFilterConditionPB::DateAfter, + timestamp: Some(1651366800), + ..Default::default() + } + }; + + let create_number_filter = || -> NumberFilterPB { + NumberFilterPB { + condition: NumberFilterConditionPB::NumberIsNotEmpty, + content: "".to_string(), + } + }; + + let scripts = vec![ + CreateOrFilter { + parent_filter_id: None, + changed: None, + }, + Wait { millisecond: 100 }, + AssertFilters { + expected: vec![FilterPB { + id: "".to_string(), + filter_type: FilterType::Or, + children: vec![], + data: None, + }], + }, + ]; + test.run_scripts(scripts).await; + // OR + + let or_filter = test.get_filter(FilterType::Or, None).await.unwrap(); + + let checkbox_filter_bytes: Result = create_checkbox_filter().try_into(); + let checkbox_filter_bytes = checkbox_filter_bytes.unwrap().to_vec(); + + let scripts = vec![ + CreateDataFilter { + parent_filter_id: Some(or_filter.id.clone()), + field_type: FieldType::Checkbox, + data: BoxAny::new(create_checkbox_filter()), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 4, + }), + }, + CreateAndFilter { + parent_filter_id: Some(or_filter.id), + changed: None, + }, + Wait { millisecond: 100 }, + AssertFilters { + expected: vec![FilterPB { + id: "".to_string(), + filter_type: FilterType::Or, + children: vec![ + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::Checkbox, + data: checkbox_filter_bytes.clone(), + }), + }, + FilterPB { + id: "".to_string(), + filter_type: FilterType::And, + children: vec![], + data: None, + }, + ], + data: None, + }], + }, + AssertNumberOfVisibleRows { expected: 3 }, + ]; + test.run_scripts(scripts).await; + // IS_CHECK OR AND + + let and_filter = test.get_filter(FilterType::And, None).await.unwrap(); + + let date_filter_bytes: Result = create_date_filter().try_into(); + let date_filter_bytes = date_filter_bytes.unwrap().to_vec(); + let number_filter_bytes: Result = create_number_filter().try_into(); + let number_filter_bytes = number_filter_bytes.unwrap().to_vec(); + + let scripts = vec![ + CreateDataFilter { + parent_filter_id: Some(and_filter.id.clone()), + field_type: FieldType::DateTime, + data: BoxAny::new(create_date_filter()), + changed: None, + }, + CreateDataFilter { + parent_filter_id: Some(and_filter.id), + field_type: FieldType::Number, + data: BoxAny::new(create_number_filter()), + changed: None, + }, + Wait { millisecond: 100 }, + AssertFilters { + expected: vec![FilterPB { + id: "".to_string(), + filter_type: FilterType::Or, + children: vec![ + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::Checkbox, + data: checkbox_filter_bytes, + }), + }, + FilterPB { + id: "".to_string(), + filter_type: FilterType::And, + children: vec![ + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::DateTime, + data: date_filter_bytes, + }), + }, + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::Number, + data: number_filter_bytes, + }), + }, + ], + data: None, + }, + ], + data: None, + }], + }, + AssertNumberOfVisibleRows { expected: 4 }, + ]; + test.run_scripts(scripts).await; + // IS_CHECK OR (DATE > 1651366800 AND NUMBER NOT EMPTY) +} + +/// Create the same advanced filter single advanced filter: +/// +/// 1. Add an OR filter +/// 2. Add a Checkbox and a DateTime filter to its children +/// 3. Add a Number filter to the DateTime filter's children +/// +#[tokio::test] +async fn create_advanced_filter_with_conversion_test() { + let mut test = DatabaseFilterTest::new().await; + + let create_checkbox_filter = || -> CheckboxFilterPB { + CheckboxFilterPB { + condition: CheckboxFilterConditionPB::IsChecked, + } + }; + + let create_date_filter = || -> DateFilterPB { + DateFilterPB { + condition: DateFilterConditionPB::DateAfter, + timestamp: Some(1651366800), + ..Default::default() + } + }; + + let create_number_filter = || -> NumberFilterPB { + NumberFilterPB { + condition: NumberFilterConditionPB::NumberIsNotEmpty, + content: "".to_string(), + } + }; + + let scripts = vec![CreateOrFilter { + parent_filter_id: None, + changed: None, + }]; + test.run_scripts(scripts).await; + // IS_CHECK OR DATE > 1651366800 + + let or_filter = test.get_filter(FilterType::Or, None).await.unwrap(); + + let scripts = vec![ + CreateDataFilter { + parent_filter_id: Some(or_filter.id.clone()), + field_type: FieldType::Checkbox, + data: BoxAny::new(create_checkbox_filter()), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: 4, + }), + }, + CreateDataFilter { + parent_filter_id: Some(or_filter.id.clone()), + field_type: FieldType::DateTime, + data: BoxAny::new(create_date_filter()), + changed: None, + }, + ]; + test.run_scripts(scripts).await; + // OR + + let date_filter = test + .get_filter(FilterType::Data, Some(FieldType::DateTime)) + .await + .unwrap(); + + let checkbox_filter_bytes: Result = create_checkbox_filter().try_into(); + let checkbox_filter_bytes = checkbox_filter_bytes.unwrap().to_vec(); + let date_filter_bytes: Result = create_date_filter().try_into(); + let date_filter_bytes = date_filter_bytes.unwrap().to_vec(); + let number_filter_bytes: Result = create_number_filter().try_into(); + let number_filter_bytes = number_filter_bytes.unwrap().to_vec(); + + let scripts = vec![ + CreateDataFilter { + parent_filter_id: Some(date_filter.id), + field_type: FieldType::Number, + data: BoxAny::new(create_number_filter()), + changed: None, + }, + Wait { millisecond: 100 }, + AssertFilters { + expected: vec![FilterPB { + id: "".to_string(), + filter_type: FilterType::Or, + children: vec![ + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::Checkbox, + data: checkbox_filter_bytes, + }), + }, + FilterPB { + id: "".to_string(), + filter_type: FilterType::And, + children: vec![ + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::DateTime, + data: date_filter_bytes, + }), + }, + FilterPB { + id: "".to_string(), + filter_type: FilterType::Data, + children: vec![], + data: Some(FilterDataPB { + field_id: "".to_string(), + field_type: FieldType::Number, + data: number_filter_bytes, + }), + }, + ], + data: None, + }, + ], + data: None, + }], + }, + AssertNumberOfVisibleRows { expected: 4 }, + ]; + test.run_scripts(scripts).await; + // IS_CHECK OR (DATE > 1651366800 AND NUMBER NOT EMPTY) +} diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs index dd30c75df6..881a1cebf9 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checkbox_filter_test.rs @@ -1,4 +1,5 @@ -use flowy_database2::entities::CheckboxFilterConditionPB; +use flowy_database2::entities::{CheckboxFilterConditionPB, CheckboxFilterPB, FieldType}; +use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; @@ -6,27 +7,39 @@ use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged} #[tokio::test] async fn grid_filter_checkbox_is_check_test() { let mut test = DatabaseFilterTest::new().await; + let expected = 3; let row_count = test.row_details.len(); - // The initial number of unchecked is 3 - // The initial number of checked is 2 - let scripts = vec![CreateCheckboxFilter { - condition: CheckboxFilterConditionPB::IsChecked, - changed: Some(FilterRowChanged { - showing_num_of_rows: 0, - hiding_num_of_rows: row_count - 3, - }), - }]; - test.run_scripts(scripts).await; -} - -#[tokio::test] -async fn grid_filter_checkbox_is_uncheck_test() { - let mut test = DatabaseFilterTest::new().await; - let expected = 4; - let row_count = test.row_details.len(); + // The initial number of checked is 3 + // The initial number of unchecked is 4 let scripts = vec![ - CreateCheckboxFilter { - condition: CheckboxFilterConditionPB::IsUnChecked, + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Checkbox, + data: BoxAny::new(CheckboxFilterPB { + condition: CheckboxFilterConditionPB::IsChecked, + }), + changed: Some(FilterRowChanged { + showing_num_of_rows: 0, + hiding_num_of_rows: row_count - expected, + }), + }, + AssertNumberOfVisibleRows { expected }, + ]; + test.run_scripts(scripts).await; +} + +#[tokio::test] +async fn grid_filter_checkbox_is_uncheck_test() { + let mut test = DatabaseFilterTest::new().await; + let expected = 4; + let row_count = test.row_details.len(); + let scripts = vec![ + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Checkbox, + data: BoxAny::new(CheckboxFilterPB { + condition: CheckboxFilterConditionPB::IsUnChecked, + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs index b6bbfc88f6..3da9cab5a2 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/checklist_filter_test.rs @@ -1,5 +1,6 @@ -use flowy_database2::entities::{ChecklistFilterConditionPB, FieldType}; +use flowy_database2::entities::{ChecklistFilterConditionPB, ChecklistFilterPB, FieldType}; use flowy_database2::services::field::checklist_type_option::ChecklistCellData; +use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; @@ -16,8 +17,12 @@ async fn grid_filter_checklist_is_incomplete_test() { row_id: test.row_details[0].row.id.clone(), selected_option_ids: option_ids, }, - CreateChecklistFilter { - condition: ChecklistFilterConditionPB::IsIncomplete, + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Checklist, + data: BoxAny::new(ChecklistFilterPB { + condition: ChecklistFilterConditionPB::IsIncomplete, + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -39,8 +44,12 @@ async fn grid_filter_checklist_is_complete_test() { row_id: test.row_details[0].row.id.clone(), selected_option_ids: option_ids, }, - CreateChecklistFilter { - condition: ChecklistFilterConditionPB::IsComplete, + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Checklist, + data: BoxAny::new(ChecklistFilterPB { + condition: ChecklistFilterConditionPB::IsComplete, + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs index 86caf2d8fa..34964b9720 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/date_filter_test.rs @@ -1,4 +1,5 @@ -use flowy_database2::entities::DateFilterConditionPB; +use flowy_database2::entities::{DateFilterConditionPB, DateFilterPB, FieldType}; +use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; @@ -9,11 +10,15 @@ async fn grid_filter_date_is_test() { let row_count = test.row_details.len(); let expected = 3; let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateIs, - start: None, - end: None, - timestamp: Some(1647251762), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::DateTime, + data: BoxAny::new(DateFilterPB { + condition: DateFilterConditionPB::DateIs, + start: None, + end: None, + timestamp: Some(1647251762), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -30,11 +35,15 @@ async fn grid_filter_date_after_test() { let row_count = test.row_details.len(); let expected = 3; let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateAfter, - start: None, - end: None, - timestamp: Some(1647251762), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::DateTime, + data: BoxAny::new(DateFilterPB { + condition: DateFilterConditionPB::DateAfter, + start: None, + end: None, + timestamp: Some(1647251762), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -51,11 +60,15 @@ async fn grid_filter_date_on_or_after_test() { let row_count = test.row_details.len(); let expected = 3; let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateOnOrAfter, - start: None, - end: None, - timestamp: Some(1668359085), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::DateTime, + data: BoxAny::new(DateFilterPB { + condition: DateFilterConditionPB::DateOnOrAfter, + start: None, + end: None, + timestamp: Some(1668359085), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -72,11 +85,15 @@ async fn grid_filter_date_on_or_before_test() { let row_count = test.row_details.len(); let expected = 4; let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateOnOrBefore, - start: None, - end: None, - timestamp: Some(1668359085), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::DateTime, + data: BoxAny::new(DateFilterPB { + condition: DateFilterConditionPB::DateOnOrBefore, + start: None, + end: None, + timestamp: Some(1668359085), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -93,11 +110,15 @@ async fn grid_filter_date_within_test() { let row_count = test.row_details.len(); let expected = 5; let scripts = vec![ - CreateDateFilter { - condition: DateFilterConditionPB::DateWithIn, - start: Some(1647251762), - end: Some(1668704685), - timestamp: None, + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::DateTime, + data: BoxAny::new(DateFilterPB { + condition: DateFilterConditionPB::DateWithIn, + start: Some(1647251762), + end: Some(1668704685), + timestamp: None, + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs index 160bf3427f..bf5d1513c9 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/mod.rs @@ -1,3 +1,4 @@ +mod advanced_filter_test; mod checkbox_filter_test; mod checklist_filter_test; mod date_filter_test; diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs index c6cdef1db2..e041ba1b4c 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/number_filter_test.rs @@ -1,4 +1,5 @@ -use flowy_database2::entities::NumberFilterConditionPB; +use flowy_database2::entities::{FieldType, NumberFilterConditionPB, NumberFilterPB}; +use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; @@ -9,9 +10,13 @@ async fn grid_filter_number_is_equal_test() { let row_count = test.row_details.len(); let expected = 1; let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::Equal, - content: "1".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Number, + data: BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::Equal, + content: "1".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -28,9 +33,13 @@ async fn grid_filter_number_is_less_than_test() { let row_count = test.row_details.len(); let expected = 2; let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThan, - content: "3".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Number, + data: BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::LessThan, + content: "3".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -48,9 +57,13 @@ async fn grid_filter_number_is_less_than_test2() { let row_count = test.row_details.len(); let expected = 2; let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThan, - content: "$3".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Number, + data: BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::LessThan, + content: "$3".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -67,9 +80,13 @@ async fn grid_filter_number_is_less_than_or_equal_test() { let row_count = test.row_details.len(); let expected = 3; let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::LessThanOrEqualTo, - content: "3".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Number, + data: BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::LessThanOrEqualTo, + content: "3".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -86,9 +103,13 @@ async fn grid_filter_number_is_empty_test() { let row_count = test.row_details.len(); let expected = 2; let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::NumberIsEmpty, - content: "".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Number, + data: BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::NumberIsEmpty, + content: "".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -105,9 +126,13 @@ async fn grid_filter_number_is_not_empty_test() { let row_count = test.row_details.len(); let expected = 5; let scripts = vec![ - CreateNumberFilter { - condition: NumberFilterConditionPB::NumberIsNotEmpty, - content: "".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::Number, + data: BoxAny::new(NumberFilterPB { + condition: NumberFilterConditionPB::NumberIsNotEmpty, + content: "".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs index 1518398719..f2b58070e7 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/script.rs @@ -3,14 +3,12 @@ use std::time::Duration; use collab_database::rows::RowId; -use flowy_database2::services::filter::FilterContext; +use flowy_database2::services::filter::{FilterChangeset, FilterInner}; +use lib_infra::box_any::BoxAny; use tokio::sync::broadcast::Receiver; use flowy_database2::entities::{ - CheckboxFilterConditionPB, CheckboxFilterPB, ChecklistFilterConditionPB, ChecklistFilterPB, - DatabaseViewSettingPB, DateFilterConditionPB, DateFilterPB, DeleteFilterPayloadPB, FieldType, - FilterPB, NumberFilterConditionPB, NumberFilterPB, SelectOptionConditionPB, SelectOptionFilterPB, - TextFilterConditionPB, TextFilterPB, UpdateFilterParams, UpdateFilterPayloadPB, + DatabaseViewSettingPB, FieldType, FilterPB, FilterType, TextFilterConditionPB, TextFilterPB, }; use flowy_database2::services::database_view::DatabaseViewChanged; use lib_dispatch::prelude::af_spawn; @@ -37,12 +35,10 @@ pub enum FilterScript { option_id: String, changed: Option, }, - InsertFilter { - payload: UpdateFilterPayloadPB, - }, - CreateTextFilter { - condition: TextFilterConditionPB, - content: String, + CreateDataFilter { + parent_filter_id: Option, + field_type: FieldType, + data: BoxAny, changed: Option, }, UpdateTextFilter { @@ -51,50 +47,36 @@ pub enum FilterScript { content: String, changed: Option, }, - CreateNumberFilter { - condition: NumberFilterConditionPB, - content: String, + CreateAndFilter { + parent_filter_id: Option, changed: Option, }, - CreateCheckboxFilter { - condition: CheckboxFilterConditionPB, + CreateOrFilter { + parent_filter_id: Option, changed: Option, }, - CreateDateFilter { - condition: DateFilterConditionPB, - start: Option, - end: Option, - timestamp: Option, - changed: Option, - }, - CreateMultiSelectFilter { - condition: SelectOptionConditionPB, - option_ids: Vec, - }, - CreateSingleSelectFilter { - condition: SelectOptionConditionPB, - option_ids: Vec, - changed: Option, - }, - CreateChecklistFilter { - condition: ChecklistFilterConditionPB, - changed: Option, - }, - AssertFilterCount { - count: i32, - }, DeleteFilter { - filter_context: FilterContext, + filter_id: String, + field_id: String, changed: Option, }, - AssertFilterContent { - filter_id: String, - condition: i64, - content: String, + // CreateSimpleAdvancedFilter, + // CreateComplexAdvancedFilter, + AssertFilterCount { + count: usize, }, AssertNumberOfVisibleRows { expected: usize, }, + AssertFilters { + /// 1. assert that the filter type is correct + /// 2. if the filter is data, assert that the field_type, condition and content are correct + /// (no field_id) + /// 3. if the filter is and/or, assert that each child is correct as well. + expected: Vec, + }, + // AssertSimpleAdvancedFilter, + // AssertComplexAdvancedFilterResult, #[allow(dead_code)] AssertGridSetting { expected_setting: DatabaseViewSettingPB, @@ -118,14 +100,54 @@ impl DatabaseFilterTest { } } - pub fn view_id(&self) -> String { - self.view_id.clone() - } - pub async fn get_all_filters(&self) -> Vec { self.editor.get_all_filters(&self.view_id).await.items } + pub async fn get_filter( + &self, + filter_type: FilterType, + field_type: Option, + ) -> Option { + let filters = self.inner.editor.get_all_filters(&self.view_id).await; + + for filter in filters.items.iter() { + let result = Self::find_filter(filter, filter_type, field_type); + if result.is_some() { + return result; + } + } + + None + } + + fn find_filter( + filter: &FilterPB, + filter_type: FilterType, + field_type: Option, + ) -> Option { + match &filter.filter_type { + FilterType::And | FilterType::Or if filter.filter_type == filter_type => Some(filter.clone()), + FilterType::And | FilterType::Or => { + for child_filter in filter.children.iter() { + if let Some(result) = Self::find_filter(child_filter, filter_type, field_type) { + return Some(result); + } + } + None + }, + FilterType::Data + if filter.filter_type == filter_type + && field_type.map_or(false, |field_type| { + field_type == filter.data.clone().unwrap().field_type + }) => + { + Some(filter.clone()) + }, + _ => None, + } + } + pub async fn run_scripts(&mut self, scripts: Vec) { for script in scripts { self.run_script(script).await; @@ -139,13 +161,7 @@ impl DatabaseFilterTest { text, changed, } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); + self.subscribe_view_changed().await; self.assert_future_changed(changed).await; self.update_text_cell(row_id, &text).await.unwrap(); }, @@ -163,46 +179,35 @@ impl DatabaseFilterTest { option_id, changed, } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); + self.subscribe_view_changed().await; self.assert_future_changed(changed).await; self .update_single_select_cell(row_id, &option_id) .await .unwrap(); }, - FilterScript::InsertFilter { payload } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - self.insert_filter(payload).await; - }, - FilterScript::CreateTextFilter { - condition, - content, + FilterScript::CreateDataFilter { + parent_filter_id, + field_type, + data, changed, } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); + self.subscribe_view_changed().await; self.assert_future_changed(changed).await; - let field = self.get_first_field(FieldType::RichText); - let text_filter = TextFilterPB { condition, content }; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, text_filter); - self.insert_filter(payload).await; + let field = self.get_first_field(field_type); + let params = FilterChangeset::Insert { + parent_filter_id, + data: FilterInner::Data { + field_id: field.id, + field_type, + condition_and_content: data, + }, + }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); }, FilterScript::UpdateTextFilter { filter, @@ -210,172 +215,76 @@ impl DatabaseFilterTest { content, changed, } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - self.assert_future_changed(changed).await; - let params = UpdateFilterParams { - view_id: self.view_id(), - field_id: filter.field_id, - filter_id: Some(filter.id), - field_type: filter.field_type, - condition: condition as i64, - content, - }; - self.editor.create_or_update_filter(params).await.unwrap(); - }, - FilterScript::CreateNumberFilter { - condition, - content, - changed, - } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - self.assert_future_changed(changed).await; - let field = self.get_first_field(FieldType::Number); - let number_filter = NumberFilterPB { condition, content }; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, number_filter); - self.insert_filter(payload).await; - }, - FilterScript::CreateCheckboxFilter { condition, changed } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - self.assert_future_changed(changed).await; - let field = self.get_first_field(FieldType::Checkbox); - let checkbox_filter = CheckboxFilterPB { condition }; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, checkbox_filter); - self.insert_filter(payload).await; - }, - FilterScript::CreateDateFilter { - condition, - start, - end, - timestamp, - changed, - } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - self.assert_future_changed(changed).await; - let field = self.get_first_field(FieldType::DateTime); - let date_filter = DateFilterPB { - condition, - start, - end, - timestamp, - }; + self.subscribe_view_changed().await; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, date_filter); - self.insert_filter(payload).await; - }, - FilterScript::CreateMultiSelectFilter { - condition, - option_ids, - } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); - let field = self.get_first_field(FieldType::MultiSelect); - let filter = SelectOptionFilterPB { - condition, - option_ids, + self.assert_future_changed(changed).await; + let current_filter = filter.data.unwrap(); + let params = FilterChangeset::UpdateData { + filter_id: filter.id, + data: FilterInner::Data { + field_id: current_filter.field_id, + field_type: current_filter.field_type, + condition_and_content: BoxAny::new(TextFilterPB { condition, content }), + }, }; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter); - self.insert_filter(payload).await; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); }, - FilterScript::CreateSingleSelectFilter { - condition, - option_ids, + FilterScript::CreateAndFilter { + parent_filter_id, changed, } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); + self.subscribe_view_changed().await; self.assert_future_changed(changed).await; - let field = self.get_first_field(FieldType::SingleSelect); - let filter = SelectOptionFilterPB { - condition, - option_ids, + let params = FilterChangeset::Insert { + parent_filter_id, + data: FilterInner::And { children: vec![] }, }; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter); - self.insert_filter(payload).await; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); }, - FilterScript::CreateChecklistFilter { condition, changed } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); + FilterScript::CreateOrFilter { + parent_filter_id, + changed, + } => { + self.subscribe_view_changed().await; self.assert_future_changed(changed).await; - let field = self.get_first_field(FieldType::Checklist); - let filter = ChecklistFilterPB { condition }; - let payload = UpdateFilterPayloadPB::new(&self.view_id(), &field, filter); - self.insert_filter(payload).await; + let params = FilterChangeset::Insert { + parent_filter_id, + data: FilterInner::Or { children: vec![] }, + }; + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); }, FilterScript::AssertFilterCount { count } => { let filters = self.editor.get_all_filters(&self.view_id).await.items; - assert_eq!(count as usize, filters.len()); - }, - FilterScript::AssertFilterContent { - filter_id, - condition, - content, - } => { - let filter = self - .editor - .get_filter(&self.view_id, &filter_id) - .await - .unwrap(); - assert_eq!(&filter.content, &content); - assert_eq!(filter.condition, condition); + assert_eq!(count, filters.len()); }, FilterScript::DeleteFilter { - filter_context, + filter_id, + field_id, changed, } => { - self.recv = Some( - self - .editor - .subscribe_view_changed(&self.view_id()) - .await - .unwrap(), - ); + self.subscribe_view_changed().await; self.assert_future_changed(changed).await; - let params = DeleteFilterPayloadPB { - filter_id: filter_context.filter_id, - view_id: self.view_id(), - field_id: filter_context.field_id, - field_type: filter_context.field_type, + let params = FilterChangeset::Delete { + filter_id, + field_id, }; - self.editor.delete_filter(params).await.unwrap(); + self + .editor + .modify_view_filters(&self.view_id, params) + .await + .unwrap(); }, FilterScript::AssertGridSetting { expected_setting } => { let setting = self @@ -385,6 +294,12 @@ impl DatabaseFilterTest { .unwrap(); assert_eq!(expected_setting, setting); }, + FilterScript::AssertFilters { expected } => { + let actual = self.get_all_filters().await; + for (actual_filter, expected_filter) in actual.iter().zip(expected.iter()) { + Self::assert_filter(actual_filter, expected_filter); + } + }, FilterScript::AssertNumberOfVisibleRows { expected } => { let grid = self.editor.get_database_data(&self.view_id).await.unwrap(); assert_eq!(grid.rows.len(), expected); @@ -395,6 +310,16 @@ impl DatabaseFilterTest { } } + async fn subscribe_view_changed(&mut self) { + self.recv = Some( + self + .editor + .subscribe_view_changed(&self.view_id) + .await + .unwrap(), + ); + } + async fn assert_future_changed(&mut self, change: Option) { if change.is_none() { return; @@ -424,9 +349,24 @@ impl DatabaseFilterTest { }); } - async fn insert_filter(&self, payload: UpdateFilterPayloadPB) { - let params: UpdateFilterParams = payload.try_into().unwrap(); - self.editor.create_or_update_filter(params).await.unwrap(); + fn assert_filter(actual: &FilterPB, expected: &FilterPB) { + assert_eq!(actual.filter_type, expected.filter_type); + assert_eq!(actual.children.is_empty(), expected.children.is_empty()); + assert_eq!(actual.data.is_some(), expected.data.is_some()); + + match actual.filter_type { + FilterType::Data => { + let actual_data = actual.data.clone().unwrap(); + let expected_data = expected.data.clone().unwrap(); + assert_eq!(actual_data.field_type, expected_data.field_type); + assert_eq!(actual_data.data, expected_data.data); + }, + FilterType::And | FilterType::Or => { + for (actual_child, expected_child) in actual.children.iter().zip(expected.children.iter()) { + Self::assert_filter(actual_child, expected_child); + } + }, + } } } diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs index 16c848ea12..58e20aea43 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/select_option_filter_test.rs @@ -1,4 +1,5 @@ -use flowy_database2::entities::{FieldType, SelectOptionConditionPB}; +use flowy_database2::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB}; +use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged}; @@ -7,9 +8,14 @@ use crate::database::filter_test::script::{DatabaseFilterTest, FilterRowChanged} async fn grid_filter_multi_select_is_empty_test() { let mut test = DatabaseFilterTest::new().await; let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::MultiSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsEmpty, + option_ids: vec![], + }), + changed: None, }, AssertNumberOfVisibleRows { expected: 2 }, ]; @@ -20,9 +26,14 @@ async fn grid_filter_multi_select_is_empty_test() { async fn grid_filter_multi_select_is_not_empty_test() { let mut test = DatabaseFilterTest::new().await; let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIsNotEmpty, - option_ids: vec![], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::MultiSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsNotEmpty, + option_ids: vec![], + }), + changed: None, }, AssertNumberOfVisibleRows { expected: 5 }, ]; @@ -35,9 +46,14 @@ async fn grid_filter_multi_select_is_test() { let field = test.get_first_field(FieldType::MultiSelect); let mut options = test.get_multi_select_type_option(&field.id); let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(0).id, options.remove(0).id], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::MultiSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![options.remove(0).id, options.remove(0).id], + }), + changed: None, }, AssertNumberOfVisibleRows { expected: 5 }, ]; @@ -50,9 +66,14 @@ async fn grid_filter_multi_select_is_test2() { let field = test.get_first_field(FieldType::MultiSelect); let mut options = test.get_multi_select_type_option(&field.id); let scripts = vec![ - CreateMultiSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(1).id], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::MultiSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![options.remove(1).id], + }), + changed: None, }, AssertNumberOfVisibleRows { expected: 4 }, ]; @@ -65,9 +86,13 @@ async fn grid_filter_single_select_is_empty_test() { let expected = 3; let row_count = test.row_details.len(); let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIsEmpty, - option_ids: vec![], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::SingleSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIsEmpty, + option_ids: vec![], + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -86,9 +111,13 @@ async fn grid_filter_single_select_is_test() { let expected = 2; let row_count = test.row_details.len(); let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![options.remove(0).id], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::SingleSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![options.remove(0).id], + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - expected, @@ -109,9 +138,13 @@ async fn grid_filter_single_select_is_test2() { let row_count = test.row_details.len(); let scripts = vec![ - CreateSingleSelectFilter { - condition: SelectOptionConditionPB::OptionIs, - option_ids: vec![option.id.clone()], + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::SingleSelect, + data: BoxAny::new(SelectOptionFilterPB { + condition: SelectOptionConditionPB::OptionIs, + option_ids: vec![option.id.clone()], + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: row_count - 2, diff --git a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs index 3c4940d261..076dc267dd 100644 --- a/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs +++ b/frontend/rust-lib/flowy-database2/tests/database/filter_test/text_filter_test.rs @@ -1,7 +1,5 @@ -use flowy_database2::entities::{ - FieldType, TextFilterConditionPB, TextFilterPB, UpdateFilterPayloadPB, -}; -use flowy_database2::services::filter::FilterContext; +use flowy_database2::entities::{FieldType, TextFilterConditionPB, TextFilterPB}; +use lib_infra::box_any::BoxAny; use crate::database::filter_test::script::FilterScript::*; use crate::database::filter_test::script::*; @@ -10,9 +8,13 @@ use crate::database::filter_test::script::*; async fn grid_filter_text_is_empty_test() { let mut test = DatabaseFilterTest::new().await; let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::TextIsEmpty, + content: "".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 5, @@ -28,9 +30,13 @@ async fn grid_filter_text_is_not_empty_test() { let mut test = DatabaseFilterTest::new().await; // Only one row's text of the initial rows is "" let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsNotEmpty, - content: "".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::TextIsNotEmpty, + content: "".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 1, @@ -44,7 +50,8 @@ async fn grid_filter_text_is_not_empty_test() { test .run_scripts(vec![ DeleteFilter { - filter_context: FilterContext::from(&filter), + filter_id: filter.id, + field_id: filter.data.unwrap().field_id, changed: Some(FilterRowChanged { showing_num_of_rows: 1, hiding_num_of_rows: 0, @@ -59,9 +66,13 @@ async fn grid_filter_text_is_not_empty_test() { async fn grid_filter_is_text_test() { let mut test = DatabaseFilterTest::new().await; // Only one row's text of the initial rows is "A" - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::Is, - content: "A".to_string(), + let scripts = vec![CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::Is, + content: "A".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 5, @@ -73,9 +84,13 @@ async fn grid_filter_is_text_test() { #[tokio::test] async fn grid_filter_contain_text_test() { let mut test = DatabaseFilterTest::new().await; - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::Contains, - content: "A".to_string(), + let scripts = vec![CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::Contains, + content: "A".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 2, @@ -90,9 +105,13 @@ async fn grid_filter_contain_text_test2() { let row_detail = test.row_details.clone(); let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::Contains, - content: "A".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::Contains, + content: "A".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 2, @@ -114,9 +133,13 @@ async fn grid_filter_contain_text_test2() { async fn grid_filter_does_not_contain_text_test() { let mut test = DatabaseFilterTest::new().await; // None of the initial rows contains the text "AB" - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::DoesNotContain, - content: "AB".to_string(), + let scripts = vec![CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::DoesNotContain, + content: "AB".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 0, @@ -128,9 +151,13 @@ async fn grid_filter_does_not_contain_text_test() { #[tokio::test] async fn grid_filter_start_with_text_test() { let mut test = DatabaseFilterTest::new().await; - let scripts = vec![CreateTextFilter { - condition: TextFilterConditionPB::StartsWith, - content: "A".to_string(), + let scripts = vec![CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::StartsWith, + content: "A".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 3, @@ -143,9 +170,13 @@ async fn grid_filter_start_with_text_test() { async fn grid_filter_ends_with_text_test() { let mut test = DatabaseFilterTest::new().await; let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::EndsWith, - content: "A".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::EndsWith, + content: "A".to_string(), + }), changed: None, }, AssertNumberOfVisibleRows { expected: 2 }, @@ -157,9 +188,13 @@ async fn grid_filter_ends_with_text_test() { async fn grid_update_text_filter_test() { let mut test = DatabaseFilterTest::new().await; let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::EndsWith, - content: "A".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::EndsWith, + content: "A".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 4, @@ -190,14 +225,16 @@ async fn grid_update_text_filter_test() { #[tokio::test] async fn grid_filter_delete_test() { let mut test = DatabaseFilterTest::new().await; - let field = test.get_first_field(FieldType::RichText).clone(); - let text_filter = TextFilterPB { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), - }; - let payload = UpdateFilterPayloadPB::new(&test.view_id(), &field, text_filter); let scripts = vec![ - InsertFilter { payload }, + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + changed: None, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::TextIsEmpty, + content: "".to_string(), + }), + }, AssertFilterCount { count: 1 }, AssertNumberOfVisibleRows { expected: 1 }, ]; @@ -207,7 +244,8 @@ async fn grid_filter_delete_test() { test .run_scripts(vec![ DeleteFilter { - filter_context: FilterContext::from(&filter), + filter_id: filter.id, + field_id: filter.data.unwrap().field_id, changed: None, }, AssertFilterCount { count: 0 }, @@ -221,9 +259,13 @@ async fn grid_filter_update_empty_text_cell_test() { let mut test = DatabaseFilterTest::new().await; let row_details = test.row_details.clone(); let scripts = vec![ - CreateTextFilter { - condition: TextFilterConditionPB::TextIsEmpty, - content: "".to_string(), + CreateDataFilter { + parent_filter_id: None, + field_type: FieldType::RichText, + data: BoxAny::new(TextFilterPB { + condition: TextFilterConditionPB::TextIsEmpty, + content: "".to_string(), + }), changed: Some(FilterRowChanged { showing_num_of_rows: 0, hiding_num_of_rows: 5, diff --git a/frontend/rust-lib/lib-infra/src/box_any.rs b/frontend/rust-lib/lib-infra/src/box_any.rs index 1822cd1a23..c471e14735 100644 --- a/frontend/rust-lib/lib-infra/src/box_any.rs +++ b/frontend/rust-lib/lib-infra/src/box_any.rs @@ -2,6 +2,7 @@ use std::any::Any; use anyhow::Result; +#[derive(Debug)] pub struct BoxAny(Box); impl BoxAny { @@ -12,6 +13,13 @@ impl BoxAny { Self(Box::new(value)) } + pub fn cloned(&self) -> Option + where + T: Clone + 'static, + { + self.0.downcast_ref::().cloned() + } + pub fn unbox_or_default(self) -> T where T: Default + 'static,