fix: number and select filter logic (#4828)

* fix: number and select option filter bugs

* chore: rename filter condition enum and variants
This commit is contained in:
Richard Shiue 2024-03-14 14:27:57 +08:00 committed by GitHub
parent 1a34366a3f
commit 5a837a9482
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
57 changed files with 1338 additions and 1266 deletions

View File

@ -257,7 +257,7 @@
"label": "AF: Tauri UI Dev",
"type": "shell",
"isBackground": true,
"command": "pnpm run sync:i18n && pnpm run dev",
"command": "pnpm sync:i18n && pnpm run dev",
"options": {
"cwd": "${workspaceFolder}/appflowy_tauri"
}

View File

@ -103,8 +103,8 @@ void main() {
// select the option 's4'
await tester.tapOptionFilterWithName('s4');
// The row with 's4' or 's5' should be shown.
await tester.assertNumberOfRowsInGridPage(2);
// The row with 's4' should be shown.
await tester.assertNumberOfRowsInGridPage(1);
await tester.pumpAndSettle();
});

View File

@ -171,7 +171,7 @@ class FilterBackendService {
Future<FlowyResult<void, FlowyError>> insertSelectOptionFilter({
required String fieldId,
required FieldType fieldType,
required SelectOptionConditionPB condition,
required SelectOptionFilterConditionPB condition,
String? filterId,
List<String> optionIds = const [],
}) {

View File

@ -114,7 +114,7 @@ class GridCreateFilterBloc
case FieldType.MultiSelect:
return _filterBackendSvc.insertSelectOptionFilter(
fieldId: fieldId,
condition: SelectOptionConditionPB.OptionIs,
condition: SelectOptionFilterConditionPB.OptionContains,
fieldType: FieldType.MultiSelect,
);
case FieldType.Checklist:
@ -130,19 +130,19 @@ class GridCreateFilterBloc
case FieldType.RichText:
return _filterBackendSvc.insertTextFilter(
fieldId: fieldId,
condition: TextFilterConditionPB.Contains,
condition: TextFilterConditionPB.TextContains,
content: '',
);
case FieldType.SingleSelect:
return _filterBackendSvc.insertSelectOptionFilter(
fieldId: fieldId,
condition: SelectOptionConditionPB.OptionIs,
condition: SelectOptionFilterConditionPB.OptionIs,
fieldType: FieldType.SingleSelect,
);
case FieldType.URL:
return _filterBackendSvc.insertURLFilter(
fieldId: fieldId,
condition: TextFilterConditionPB.Contains,
condition: TextFilterConditionPB.TextContains,
);
default:
throw UnimplementedError();

View File

@ -38,7 +38,7 @@ class SelectOptionFilterEditorBloc
_startListening();
_loadOptions();
},
updateCondition: (SelectOptionConditionPB condition) {
updateCondition: (SelectOptionFilterConditionPB condition) {
_filterBackendSvc.insertSelectOptionFilter(
filterId: filterInfo.filter.id,
fieldId: filterInfo.fieldInfo.id,
@ -117,7 +117,7 @@ class SelectOptionFilterEditorEvent with _$SelectOptionFilterEditorEvent {
FilterPB filter,
) = _DidReceiveFilter;
const factory SelectOptionFilterEditorEvent.updateCondition(
SelectOptionConditionPB condition,
SelectOptionFilterConditionPB condition,
) = _UpdateCondition;
const factory SelectOptionFilterEditorEvent.updateContent(
List<String> optionIds,

View File

@ -1,5 +1,5 @@
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/choicechip/select_option/select_option_loader.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import 'package:freezed_annotation/freezed_annotation.dart';
@ -24,9 +24,12 @@ class SelectOptionFilterListBloc<T>
_startListening();
_loadOptions();
},
selectOption: (option) {
final selectedOptionIds = Set<String>.from(state.selectedOptionIds);
selectedOptionIds.add(option.id);
selectOption: (option, condition) {
final selectedOptionIds = delegate.selectOption(
state.selectedOptionIds,
option.id,
condition,
);
_updateSelectOptions(
selectedOptionIds: selectedOptionIds,
@ -116,6 +119,7 @@ class SelectOptionFilterListEvent with _$SelectOptionFilterListEvent {
const factory SelectOptionFilterListEvent.initial() = _Initial;
const factory SelectOptionFilterListEvent.selectOption(
SelectOptionPB option,
SelectOptionFilterConditionPB condition,
) = _SelectOption;
const factory SelectOptionFilterListEvent.unselectOption(
SelectOptionPB option,

View File

@ -1,15 +1,12 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/generated/locale_keys.g.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/condition_button.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart';
import 'package:appflowy/workspace/presentation/widgets/pop_up_action.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
import 'package:appflowy_popover/appflowy_popover.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/field_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_filter.pb.dart';
import 'package:flutter/material.dart';
import '../../condition_button.dart';
import '../../filter_info.dart';
import 'package:flutter/widgets.dart';
class SelectOptionFilterConditionList extends StatelessWidget {
const SelectOptionFilterConditionList({
@ -21,7 +18,7 @@ class SelectOptionFilterConditionList extends StatelessWidget {
final FilterInfo filterInfo;
final PopoverMutex popoverMutex;
final Function(SelectOptionConditionPB) onCondition;
final Function(SelectOptionFilterConditionPB) onCondition;
@override
Widget build(BuildContext context) {
@ -30,18 +27,17 @@ class SelectOptionFilterConditionList extends StatelessWidget {
asBarrier: true,
mutex: popoverMutex,
direction: PopoverDirection.bottomWithCenterAligned,
actions: SelectOptionConditionPB.values
actions: _conditionsForFieldType(filterInfo.fieldInfo.fieldType)
.map(
(action) => ConditionWrapper(
action,
selectOptionFilter.condition == action,
filterInfo.fieldInfo.fieldType,
),
)
.toList(),
buildChild: (controller) {
return ConditionButton(
conditionName: filterName(selectOptionFilter),
conditionName: selectOptionFilter.condition.i18n,
onTap: () => controller.show(),
);
},
@ -52,69 +48,62 @@ class SelectOptionFilterConditionList extends StatelessWidget {
);
}
String filterName(SelectOptionFilterPB filter) {
if (filterInfo.fieldInfo.fieldType == FieldType.SingleSelect) {
return filter.condition.singleSelectFilterName;
} else {
return filter.condition.multiSelectFilterName;
}
List<SelectOptionFilterConditionPB> _conditionsForFieldType(
FieldType fieldType,
) {
// SelectOptionFilterConditionPB.values is not in order
return switch (fieldType) {
FieldType.SingleSelect => [
SelectOptionFilterConditionPB.OptionIs,
SelectOptionFilterConditionPB.OptionIsNot,
SelectOptionFilterConditionPB.OptionIsEmpty,
SelectOptionFilterConditionPB.OptionIsNotEmpty,
],
FieldType.MultiSelect => [
SelectOptionFilterConditionPB.OptionContains,
SelectOptionFilterConditionPB.OptionDoesNotContain,
SelectOptionFilterConditionPB.OptionIs,
SelectOptionFilterConditionPB.OptionIsNot,
SelectOptionFilterConditionPB.OptionIsEmpty,
SelectOptionFilterConditionPB.OptionIsNotEmpty,
],
_ => [],
};
}
}
class ConditionWrapper extends ActionCell {
ConditionWrapper(this.inner, this.isSelected, this.fieldType);
ConditionWrapper(this.inner, this.isSelected);
final SelectOptionConditionPB inner;
final SelectOptionFilterConditionPB inner;
final bool isSelected;
final FieldType fieldType;
@override
Widget? rightIcon(Color iconColor) {
if (isSelected) {
return const FlowySvg(FlowySvgs.check_s);
} else {
return null;
}
return isSelected ? const FlowySvg(FlowySvgs.check_s) : null;
}
@override
String get name {
if (fieldType == FieldType.SingleSelect) {
return inner.singleSelectFilterName;
} else {
return inner.multiSelectFilterName;
}
}
String get name => inner.i18n;
}
extension SelectOptionConditionPBExtension on SelectOptionConditionPB {
String get singleSelectFilterName {
switch (this) {
case SelectOptionConditionPB.OptionIs:
return LocaleKeys.grid_singleSelectOptionFilter_is.tr();
case SelectOptionConditionPB.OptionIsEmpty:
return LocaleKeys.grid_singleSelectOptionFilter_isEmpty.tr();
case SelectOptionConditionPB.OptionIsNot:
return LocaleKeys.grid_singleSelectOptionFilter_isNot.tr();
case SelectOptionConditionPB.OptionIsNotEmpty:
return LocaleKeys.grid_singleSelectOptionFilter_isNotEmpty.tr();
default:
return "";
}
}
String get multiSelectFilterName {
switch (this) {
case SelectOptionConditionPB.OptionIs:
return LocaleKeys.grid_multiSelectOptionFilter_contains.tr();
case SelectOptionConditionPB.OptionIsEmpty:
return LocaleKeys.grid_multiSelectOptionFilter_isEmpty.tr();
case SelectOptionConditionPB.OptionIsNot:
return LocaleKeys.grid_multiSelectOptionFilter_doesNotContain.tr();
case SelectOptionConditionPB.OptionIsNotEmpty:
return LocaleKeys.grid_multiSelectOptionFilter_isNotEmpty.tr();
default:
return "";
}
extension SelectOptionFilterConditionPBExtension
on SelectOptionFilterConditionPB {
String get i18n {
return switch (this) {
SelectOptionFilterConditionPB.OptionIs =>
LocaleKeys.grid_selectOptionFilter_is.tr(),
SelectOptionFilterConditionPB.OptionIsNot =>
LocaleKeys.grid_selectOptionFilter_isNot.tr(),
SelectOptionFilterConditionPB.OptionContains =>
LocaleKeys.grid_selectOptionFilter_isNot.tr(),
SelectOptionFilterConditionPB.OptionDoesNotContain =>
LocaleKeys.grid_selectOptionFilter_isNot.tr(),
SelectOptionFilterConditionPB.OptionIsEmpty =>
LocaleKeys.grid_selectOptionFilter_isEmpty.tr(),
SelectOptionFilterConditionPB.OptionIsNotEmpty =>
LocaleKeys.grid_selectOptionFilter_isNotEmpty.tr(),
_ => "",
};
}
}

View File

@ -1,4 +1,5 @@
import 'package:appflowy/generated/flowy_svgs.g.dart';
import 'package:appflowy/plugins/database/grid/application/filter/select_option_filter_bloc.dart';
import 'package:appflowy/plugins/database/grid/application/filter/select_option_filter_list_bloc.dart';
import 'package:appflowy/plugins/database/grid/presentation/layout/sizes.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart';
@ -90,9 +91,16 @@ class _SelectOptionFilterCellState extends State<SelectOptionFilterCell> {
.read<SelectOptionFilterListBloc>()
.add(SelectOptionFilterListEvent.unselectOption(widget.option));
} else {
context
.read<SelectOptionFilterListBloc>()
.add(SelectOptionFilterListEvent.selectOption(widget.option));
context.read<SelectOptionFilterListBloc>().add(
SelectOptionFilterListEvent.selectOption(
widget.option,
context
.read<SelectOptionFilterEditorBloc>()
.state
.filter
.condition,
),
);
}
},
children: [

View File

@ -101,9 +101,10 @@ class _SelectOptionFilterEditorState extends State<SelectOptionFilterEditor> {
SliverToBoxAdapter(child: _buildFilterPanel(context, state)),
];
if (state.filter.condition != SelectOptionConditionPB.OptionIsEmpty &&
if (state.filter.condition !=
SelectOptionFilterConditionPB.OptionIsEmpty &&
state.filter.condition !=
SelectOptionConditionPB.OptionIsNotEmpty) {
SelectOptionFilterConditionPB.OptionIsNotEmpty) {
slivers.add(const SliverToBoxAdapter(child: VSpace(4)));
slivers.add(
SliverToBoxAdapter(

View File

@ -1,9 +1,15 @@
import 'package:appflowy/plugins/database/application/field/type_option/type_option_data_parser.dart';
import 'package:appflowy/plugins/database/grid/presentation/widgets/filter/filter_info.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/select_option_entities.pb.dart';
import 'package:appflowy_backend/protobuf/flowy-database2/protobuf.dart';
abstract class SelectOptionFilterDelegate {
List<SelectOptionPB> loadOptions();
Set<String> selectOption(
Set<String> currentOptionIds,
String optionId,
SelectOptionFilterConditionPB condition,
);
}
class SingleSelectOptionFilterDelegateImpl
@ -17,6 +23,22 @@ class SingleSelectOptionFilterDelegateImpl
final parser = SingleSelectTypeOptionDataParser();
return parser.fromBuffer(filterInfo.fieldInfo.field.typeOptionData).options;
}
@override
Set<String> selectOption(
Set<String> currentOptionIds,
String optionId,
SelectOptionFilterConditionPB condition,
) {
final selectOptionIds = Set<String>.from(currentOptionIds);
if (condition == SelectOptionFilterConditionPB.OptionIsNot ||
selectOptionIds.isEmpty) {
selectOptionIds.add(optionId);
}
return selectOptionIds;
}
}
class MultiSelectOptionFilterDelegateImpl
@ -30,4 +52,12 @@ class MultiSelectOptionFilterDelegateImpl
final parser = MultiSelectTypeOptionDataParser();
return parser.fromBuffer(filterInfo.fieldInfo.field.typeOptionData).options;
}
@override
Set<String> selectOption(
Set<String> currentOptionIds,
String optionId,
SelectOptionFilterConditionPB condition,
) =>
Set<String>.from(currentOptionIds)..add(optionId);
}

View File

@ -236,17 +236,17 @@ class ConditionWrapper extends ActionCell {
extension TextFilterConditionPBExtension on TextFilterConditionPB {
String get filterName {
switch (this) {
case TextFilterConditionPB.Contains:
case TextFilterConditionPB.TextContains:
return LocaleKeys.grid_textFilter_contains.tr();
case TextFilterConditionPB.DoesNotContain:
case TextFilterConditionPB.TextDoesNotContain:
return LocaleKeys.grid_textFilter_doesNotContain.tr();
case TextFilterConditionPB.EndsWith:
case TextFilterConditionPB.TextEndsWith:
return LocaleKeys.grid_textFilter_endsWith.tr();
case TextFilterConditionPB.Is:
case TextFilterConditionPB.TextIs:
return LocaleKeys.grid_textFilter_is.tr();
case TextFilterConditionPB.IsNot:
case TextFilterConditionPB.TextIsNot:
return LocaleKeys.grid_textFilter_isNot.tr();
case TextFilterConditionPB.StartsWith:
case TextFilterConditionPB.TextStartsWith:
return LocaleKeys.grid_textFilter_startWith.tr();
case TextFilterConditionPB.TextIsEmpty:
return LocaleKeys.grid_textFilter_isEmpty.tr();
@ -259,13 +259,13 @@ extension TextFilterConditionPBExtension on TextFilterConditionPB {
String get choicechipPrefix {
switch (this) {
case TextFilterConditionPB.DoesNotContain:
case TextFilterConditionPB.TextDoesNotContain:
return LocaleKeys.grid_textFilter_choicechipPrefix_isNot.tr();
case TextFilterConditionPB.EndsWith:
case TextFilterConditionPB.TextEndsWith:
return LocaleKeys.grid_textFilter_choicechipPrefix_endWith.tr();
case TextFilterConditionPB.IsNot:
case TextFilterConditionPB.TextIsNot:
return LocaleKeys.grid_textFilter_choicechipPrefix_isNot.tr();
case TextFilterConditionPB.StartsWith:
case TextFilterConditionPB.TextStartsWith:
return LocaleKeys.grid_textFilter_choicechipPrefix_startWith.tr();
case TextFilterConditionPB.TextIsEmpty:
return LocaleKeys.grid_textFilter_choicechipPrefix_isEmpty.tr();

View File

@ -55,13 +55,13 @@ void main() {
await service.insertTextFilter(
fieldId: textField.id,
filterId: textFilter.filter.id,
condition: TextFilterConditionPB.Is,
condition: TextFilterConditionPB.TextIs,
content: "ABC",
);
await gridResponseFuture();
assert(
menuBloc.state.filters.first.textFilter()!.condition ==
TextFilterConditionPB.Is,
TextFilterConditionPB.TextIs,
);
assert(menuBloc.state.filters.first.textFilter()!.content == "ABC");
});

View File

@ -118,7 +118,7 @@ void main() {
// create a new filter
await service.insertTextFilter(
fieldId: textField.id,
condition: TextFilterConditionPB.Is,
condition: TextFilterConditionPB.TextIs,
content: "A",
);
await gridResponseFuture();
@ -132,7 +132,7 @@ void main() {
await service.insertTextFilter(
fieldId: textField.id,
filterId: textFilter.filter.id,
condition: TextFilterConditionPB.Is,
condition: TextFilterConditionPB.TextIs,
content: "B",
);
await gridResponseFuture();
@ -142,7 +142,7 @@ void main() {
await service.insertTextFilter(
fieldId: textField.id,
filterId: textFilter.filter.id,
condition: TextFilterConditionPB.Is,
condition: TextFilterConditionPB.TextIs,
content: "b",
);
await gridResponseFuture();
@ -152,7 +152,7 @@ void main() {
await service.insertTextFilter(
fieldId: textField.id,
filterId: textFilter.filter.id,
condition: TextFilterConditionPB.Is,
condition: TextFilterConditionPB.TextIs,
content: "C",
);
await gridResponseFuture();

View File

@ -3,6 +3,7 @@ import {
ChecklistFilterConditionPB,
FieldType,
NumberFilterConditionPB,
SelectOptionFilterConditionPB,
TextFilterConditionPB,
} from '@/services/backend';
import { UndeterminedFilter } from '$app/application/database';
@ -12,7 +13,7 @@ export function getDefaultFilter(fieldType: FieldType): UndeterminedFilter['data
case FieldType.RichText:
case FieldType.URL:
return {
condition: TextFilterConditionPB.Contains,
condition: TextFilterConditionPB.TextContains,
content: '',
};
case FieldType.Number:
@ -27,6 +28,14 @@ export function getDefaultFilter(fieldType: FieldType): UndeterminedFilter['data
return {
condition: ChecklistFilterConditionPB.IsIncomplete,
};
case FieldType.SingleSelect:
return {
condition: SelectOptionFilterConditionPB.OptionIs,
};
case FieldType.MultiSelect:
return {
condition: SelectOptionFilterConditionPB.OptionContains,
};
default:
return;
}

View File

@ -5,7 +5,7 @@ import {
FilterPB,
NumberFilterConditionPB,
NumberFilterPB,
SelectOptionConditionPB,
SelectOptionFilterConditionPB,
SelectOptionFilterPB,
TextFilterConditionPB,
TextFilterPB,
@ -66,7 +66,7 @@ export interface ChecklistFilterData {
}
export interface SelectFilterData {
condition?: SelectOptionConditionPB;
condition?: SelectOptionFilterConditionPB;
optionIds?: string[];
}

View File

@ -6,7 +6,7 @@ import {
DateFilterConditionPB,
FieldType,
NumberFilterConditionPB,
SelectOptionConditionPB,
SelectOptionFilterConditionPB,
TextFilterConditionPB,
} from '@/services/backend';
@ -30,27 +30,27 @@ function FilterConditionSelect({
case FieldType.URL:
return [
{
value: TextFilterConditionPB.Contains,
value: TextFilterConditionPB.TextContains,
text: t('grid.textFilter.contains'),
},
{
value: TextFilterConditionPB.DoesNotContain,
value: TextFilterConditionPB.TextDoesNotContain,
text: t('grid.textFilter.doesNotContain'),
},
{
value: TextFilterConditionPB.StartsWith,
value: TextFilterConditionPB.TextStartsWith,
text: t('grid.textFilter.startWith'),
},
{
value: TextFilterConditionPB.EndsWith,
value: TextFilterConditionPB.TextEndsWith,
text: t('grid.textFilter.endsWith'),
},
{
value: TextFilterConditionPB.Is,
value: TextFilterConditionPB.TextIs,
text: t('grid.textFilter.is'),
},
{
value: TextFilterConditionPB.IsNot,
value: TextFilterConditionPB.TextIsNot,
text: t('grid.textFilter.isNot'),
},
{
@ -63,26 +63,51 @@ function FilterConditionSelect({
},
];
case FieldType.SingleSelect:
return [
{
value: SelectOptionFilterConditionPB.OptionIs,
text: t('grid.selectOptionFilter.is'),
},
{
value: SelectOptionFilterConditionPB.OptionIsNot,
text: t('grid.selectOptionFilter.isNot'),
},
{
value: SelectOptionFilterConditionPB.OptionIsEmpty,
text: t('grid.selectOptionFilter.isEmpty'),
},
{
value: SelectOptionFilterConditionPB.OptionIsNotEmpty,
text: t('grid.selectOptionFilter.isNotEmpty'),
},
];
case FieldType.MultiSelect:
return [
{
value: SelectOptionConditionPB.OptionIs,
text: t('grid.singleSelectOptionFilter.is'),
value: SelectOptionFilterConditionPB.OptionIs,
text: t('grid.selectOptionFilter.is'),
},
{
value: SelectOptionConditionPB.OptionIsNot,
text: t('grid.singleSelectOptionFilter.isNot'),
value: SelectOptionFilterConditionPB.OptionIsNot,
text: t('grid.selectOptionFilter.isNot'),
},
{
value: SelectOptionConditionPB.OptionIsEmpty,
text: t('grid.singleSelectOptionFilter.isEmpty'),
value: SelectOptionFilterConditionPB.OptionContains,
text: t('grid.selectOptionFilter.contains'),
},
{
value: SelectOptionConditionPB.OptionIsNotEmpty,
text: t('grid.singleSelectOptionFilter.isNotEmpty'),
value: SelectOptionFilterConditionPB.OptionDoesNotContain,
text: t('grid.selectOptionFilter.doesNotContain'),
},
{
value: SelectOptionFilterConditionPB.OptionIsEmpty,
text: t('grid.selectOptionFilter.isEmpty'),
},
{
value: SelectOptionFilterConditionPB.OptionIsNotEmpty,
text: t('grid.selectOptionFilter.isNotEmpty'),
},
];
case FieldType.Number:
return [
{

View File

@ -7,7 +7,7 @@ import {
} from '$app/application/database';
import { Tag } from '$app/components/database/components/field_types/select/Tag';
import { ReactComponent as SelectCheckSvg } from '$app/assets/select-check.svg';
import { SelectOptionConditionPB } from '@/services/backend';
import { SelectOptionFilterConditionPB } from '@/services/backend';
import { useTypeOption } from '$app/components/database';
import KeyboardNavigation, {
KeyboardNavigationOption,
@ -42,8 +42,8 @@ function SelectFilter({ onClose, filter, field, onChange }: Props) {
const showOptions =
options.length > 0 &&
condition !== SelectOptionConditionPB.OptionIsEmpty &&
condition !== SelectOptionConditionPB.OptionIsNotEmpty;
condition !== SelectOptionFilterConditionPB.OptionIsEmpty &&
condition !== SelectOptionFilterConditionPB.OptionIsNotEmpty;
const handleChange = ({
condition,

View File

@ -2,7 +2,7 @@ import React, { useMemo } from 'react';
import { SelectFilterData, SelectTypeOption } from '$app/application/database';
import { useStaticTypeOption } from '$app/components/database';
import { useTranslation } from 'react-i18next';
import { SelectOptionConditionPB } from '@/services/backend';
import { SelectOptionFilterConditionPB } from '@/services/backend';
function SelectFilterValue({ data, fieldId }: { data: SelectFilterData; fieldId: string }) {
const typeOption = useStaticTypeOption<SelectTypeOption>(fieldId);
@ -19,13 +19,13 @@ function SelectFilterValue({ data, fieldId }: { data: SelectFilterData; fieldId:
.join(', ');
switch (data.condition) {
case SelectOptionConditionPB.OptionIs:
case SelectOptionFilterConditionPB.OptionIs:
return `: ${options}`;
case SelectOptionConditionPB.OptionIsNot:
case SelectOptionFilterConditionPB.OptionIsNot:
return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${options}`;
case SelectOptionConditionPB.OptionIsEmpty:
case SelectOptionFilterConditionPB.OptionIsEmpty:
return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`;
case SelectOptionConditionPB.OptionIsNotEmpty:
case SelectOptionFilterConditionPB.OptionIsNotEmpty:
return `: ${t('grid.textFilter.choicechipPrefix.isNotEmpty')}`;
default:
return '';

View File

@ -9,15 +9,15 @@ function TextFilterValue({ data }: { data: TextFilterData }) {
const value = useMemo(() => {
if (!data.content) return '';
switch (data.condition) {
case TextFilterConditionPB.Contains:
case TextFilterConditionPB.Is:
case TextFilterConditionPB.TextContains:
case TextFilterConditionPB.TextIs:
return `: ${data.content}`;
case TextFilterConditionPB.DoesNotContain:
case TextFilterConditionPB.IsNot:
case TextFilterConditionPB.TextDoesNotContain:
case TextFilterConditionPB.TextIsNot:
return `: ${t('grid.textFilter.choicechipPrefix.isNot')} ${data.content}`;
case TextFilterConditionPB.StartsWith:
case TextFilterConditionPB.TextStartsWith:
return `: ${t('grid.textFilter.choicechipPrefix.startWith')} ${data.content}`;
case TextFilterConditionPB.EndsWith:
case TextFilterConditionPB.TextEndsWith:
return `: ${t('grid.textFilter.choicechipPrefix.endWith')} ${data.content}`;
case TextFilterConditionPB.TextIsEmpty:
return `: ${t('grid.textFilter.choicechipPrefix.isEmpty')}`;

View File

@ -422,13 +422,9 @@
"isComplete": "አይደለም",
"isIncomplted": "ባዶ ነው"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "ነው",
"isNot": "አይደለም",
"isEmpty": "ባዶ ነው",
"isNotEmpty": "ባዶ አይደለም"
},
"multiSelectOptionFilter": {
"contains": "ይይዛል",
"doesNotContain": "አይይዝም",
"isEmpty": "ባዶ ነው",

View File

@ -486,13 +486,9 @@
"isComplete": "كاملة",
"isIncomplted": "غير مكتمل"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "يكون",
"isNot": "ليس",
"isEmpty": "فارغ",
"isNotEmpty": "ليس فارغا"
},
"multiSelectOptionFilter": {
"contains": "يتضمن",
"doesNotContain": "لا يحتوي",
"isEmpty": "فارغ",
@ -1172,4 +1168,4 @@
"addField": "إضافة حقل",
"userIcon": "رمز المستخدم"
}
}
}

View File

@ -459,13 +459,9 @@
"isComplete": "està completa",
"isIncomplted": "és incompleta"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "És",
"isNot": "No és",
"isEmpty": "Està buit",
"isNotEmpty": "No està buit"
},
"multiSelectOptionFilter": {
"contains": "Conté",
"doesNotContain": "No conté",
"isEmpty": "Està buit",
@ -811,4 +807,4 @@
"deleteContentTitle": "Esteu segur que voleu suprimir {pageType}?",
"deleteContentCaption": "si suprimiu aquest {pageType}, podeu restaurar-lo des de la paperera."
}
}
}

View File

@ -356,13 +356,9 @@
"isComplete": "تەواوە",
"isIncomplted": "ناتەواوە"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "هەیە",
"isNot": "نییە",
"isEmpty": "به‌تاڵه‌",
"isNotEmpty": "بەتاڵ نییە"
},
"multiSelectOptionFilter": {
"contains": "لەخۆ دەگرێت",
"doesNotContain": "لەخۆناگرێت",
"isEmpty": "به‌تاڵه‌",
@ -673,4 +669,4 @@
"frequentlyUsed": "زۆرجار بەکارت هێناوە"
}
}
}
}

View File

@ -503,13 +503,9 @@
"isComplete": "ist komplett",
"isIncomplted": "ist unvollständig"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Ist",
"isNot": "Ist nicht",
"isEmpty": "Ist leer",
"isNotEmpty": "Ist nicht leer"
},
"multiSelectOptionFilter": {
"contains": "Enthält",
"doesNotContain": "Beinhaltet nicht",
"isEmpty": "Ist leer",
@ -1202,4 +1198,4 @@
"addField": "Ein Feld hinzufügen",
"userIcon": "Nutzerbild"
}
}
}

View File

@ -570,13 +570,9 @@
"isComplete": "is complete",
"isIncomplted": "is incomplete"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Is",
"isNot": "Is not",
"isEmpty": "Is empty",
"isNotEmpty": "Is not empty"
},
"multiSelectOptionFilter": {
"contains": "Contains",
"doesNotContain": "Does not contain",
"isEmpty": "Is empty",

View File

@ -500,13 +500,9 @@
"isComplete": "Esta completo",
"isIncomplted": "esta incompleto"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Es",
"isNot": "No es",
"isEmpty": "Esta vacio",
"isNotEmpty": "No está vacío"
},
"multiSelectOptionFilter": {
"contains": "Contiene",
"doesNotContain": "No contiene",
"isEmpty": "Esta vacio",
@ -1063,4 +1059,4 @@
"backgroundColorPink": "fondo rosa",
"backgroundColorRed": "fondo rojo"
}
}
}

View File

@ -323,13 +323,9 @@
"isComplete": "osatu da",
"isIncomplted": "osatu gabe dago"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "da",
"isNot": "Ez da",
"isEmpty": "Hutsa dago",
"isNotEmpty": "Ez dago hutsik"
},
"multiSelectOptionFilter": {
"contains": "Duen",
"doesNotContain": "Ez dauka",
"isEmpty": "Hutsa dago",
@ -600,4 +596,4 @@
"deleteContentTitle": "Ziur {pageType} ezabatu nahi duzula?",
"deleteContentCaption": "{pageType} hau ezabatzen baduzu, zaborrontzitik leheneratu dezakezu."
}
}
}

View File

@ -356,13 +356,9 @@
"isComplete": "کامل است",
"isIncomplted": "کامل نیست"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "است",
"isNot": "نیست",
"isEmpty": "خالی است",
"isNotEmpty": "خالی نیست"
},
"multiSelectOptionFilter": {
"contains": "شامل",
"doesNotContain": "شامل نیست",
"isEmpty": "خالی است",
@ -673,4 +669,4 @@
"frequentlyUsed": "استفاده‌شده"
}
}
}
}

View File

@ -521,13 +521,9 @@
"isComplete": "fait",
"isIncomplted": "pas fait"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Est",
"isNot": "N'est pas",
"isEmpty": "Est vide",
"isNotEmpty": "N'est pas vide"
},
"multiSelectOptionFilter": {
"contains": "Contient",
"doesNotContain": "Ne contient pas",
"isEmpty": "Est vide",

View File

@ -542,13 +542,9 @@
"isComplete": "fait",
"isIncomplted": "pas fait"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Est",
"isNot": "N'est pas",
"isEmpty": "Est vide",
"isNotEmpty": "N'est pas vide"
},
"multiSelectOptionFilter": {
"contains": "Contient",
"doesNotContain": "Ne contient pas",
"isEmpty": "Est vide",

File diff suppressed because it is too large Load Diff

View File

@ -325,13 +325,9 @@
"isComplete": "teljes",
"isIncomplted": "hiányos"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Is",
"isNot": "Nem",
"isEmpty": "Üres",
"isNotEmpty": "Nem üres"
},
"multiSelectOptionFilter": {
"contains": "Tartalmaz",
"doesNotContain": "Nem tartalmaz",
"isEmpty": "Üres",
@ -598,4 +594,4 @@
"deleteContentTitle": "Biztosan törli a következőt: {pageType}?",
"deleteContentCaption": "ha törli ezt a {pageType} oldalt, visszaállíthatja a kukából."
}
}
}

View File

@ -452,13 +452,9 @@
"isComplete": "selesai",
"isIncomplted": "tidak lengkap"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Adalah",
"isNot": "Tidak",
"isEmpty": "Kosong",
"isNotEmpty": "Tidak kosong"
},
"multiSelectOptionFilter": {
"contains": "Mengandung",
"doesNotContain": "Tidak mengandung",
"isEmpty": "Kosong",
@ -1021,4 +1017,4 @@
"noFavorite": "Tidak ada halaman favorit",
"noFavoriteHintText": "Geser halaman ke kiri untuk menambahkannya ke favorit Anda"
}
}
}

View File

@ -516,13 +516,9 @@
"isComplete": "è completo",
"isIncomplted": "è incompleto"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "È",
"isNot": "Non è",
"isEmpty": "È vuoto",
"isNotEmpty": "Non è vuoto"
},
"multiSelectOptionFilter": {
"contains": "Contiene",
"doesNotContain": "Non contiene",
"isEmpty": "È vuoto",

View File

@ -412,13 +412,9 @@
"isComplete": "完了",
"isIncomplted": "未完了"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "等しい",
"isNot": "等しくない",
"isEmpty": "空である",
"isNotEmpty": "空ではない"
},
"multiSelectOptionFilter": {
"contains": "を含む",
"doesNotContain": "を含まない",
"isEmpty": "空である",
@ -685,4 +681,4 @@
"deleteContentTitle": "{pageType} を削除してもよろしいですか?",
"deleteContentCaption": "この {pageType} を削除しても、ゴミ箱から復元できます。"
}
}
}

View File

@ -324,13 +324,9 @@
"isComplete": "완료되었습니다",
"isIncomplted": "불완전하다"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "~이다",
"isNot": "아니다",
"isEmpty": "비었다",
"isNotEmpty": "비어 있지 않음"
},
"multiSelectOptionFilter": {
"contains": "포함",
"doesNotContain": "포함되어 있지 않다",
"isEmpty": "비었다",
@ -597,4 +593,4 @@
"deleteContentTitle": "{pageType}을(를) 삭제하시겠습니까?",
"deleteContentCaption": "이 {pageType}을(를) 삭제하면 휴지통에서 복원할 수 있습니다."
}
}
}

View File

@ -454,13 +454,9 @@
"isComplete": "jest kompletna",
"isIncomplted": "jest niekompletna"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Jest",
"isNot": "Nie jest",
"isEmpty": "Jest pusty",
"isNotEmpty": "Nie jest pusty"
},
"multiSelectOptionFilter": {
"contains": "Zawiera",
"doesNotContain": "Nie zawiera",
"isEmpty": "Jest pusty",
@ -1076,4 +1072,4 @@
"language": "Język",
"font": "Czcionka"
}
}
}

View File

@ -511,13 +511,9 @@
"isComplete": "está completo",
"isIncomplted": "está imcompleto"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Está",
"isNot": "Não está",
"isEmpty": "Está vazio",
"isNotEmpty": "Não está vazio"
},
"multiSelectOptionFilter": {
"contains": "Contém",
"doesNotContain": "Não contém",
"isEmpty": "Está vazio",
@ -1211,4 +1207,4 @@
"addField": "Adicionar campo",
"userIcon": "Ícone do usuário"
}
}
}

View File

@ -426,13 +426,9 @@
"isComplete": "está completo",
"isIncomplted": "está incompleto"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "É",
"isNot": "não é",
"isEmpty": "Está vazia",
"isNotEmpty": "Não está vazio"
},
"multiSelectOptionFilter": {
"contains": "contém",
"doesNotContain": "Não contém",
"isEmpty": "Está vazia",
@ -856,4 +852,4 @@
"noResult": "Nenhum resultado",
"caseSensitive": "Maiúsculas e minúsculas"
}
}
}

View File

@ -528,13 +528,9 @@
"isComplete": "завершено",
"isIncomplted": "не завершено"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Является",
"isNot": "Не является",
"isEmpty": "Пусто",
"isNotEmpty": "Не пусто"
},
"multiSelectOptionFilter": {
"contains": "Содержит",
"doesNotContain": "Не содержит",
"isEmpty": "Пусто",

View File

@ -322,13 +322,9 @@
"isComplete": "är komplett",
"isIncomplted": "är ofullständig"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Är",
"isNot": "Är inte",
"isEmpty": "Är tom",
"isNotEmpty": "Är inte tom"
},
"multiSelectOptionFilter": {
"contains": "Innehåller",
"doesNotContain": "Innehåller inte",
"isEmpty": "Är tom",
@ -595,4 +591,4 @@
"deleteContentTitle": "Är du säker på att du vill ta bort {pageType}?",
"deleteContentCaption": "om du tar bort denna {pageType} kan du återställa den från papperskorgen."
}
}
}

View File

@ -478,13 +478,9 @@
"isComplete": "เสร็จสมบูรณ์",
"isIncomplted": "ไม่เสร็จสมบูรณ์"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "เป็น",
"isNot": "ไม่เป็น",
"isEmpty": "ว่างเปล่า",
"isNotEmpty": "ไม่ว่างเปล่า"
},
"multiSelectOptionFilter": {
"contains": "ประกอบด้วย",
"doesNotContain": "ไม่ประกอบด้วย",
"isEmpty": "ว่างเปล่า",

View File

@ -454,13 +454,9 @@
"isComplete": "Tamamlanmış",
"isIncomplted": "Tamamlanmamış"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Şu olan",
"isNot": "Şu olmayan",
"isEmpty": "Boş olan",
"isNotEmpty": "Boş olmayan"
},
"multiSelectOptionFilter": {
"contains": "Şunu içeren",
"doesNotContain": "Şunu içermeyen",
"isEmpty": "Boş olan",
@ -1093,4 +1089,4 @@
"language": "Dil",
"font": "Yazı tipi"
}
}
}

View File

@ -281,7 +281,6 @@
"auto": "АВТО",
"fallback": "Такий же, як і напрямок макету"
},
"themeUpload": {
"button": "Завантажити",
"uploadTheme": "Завантажити тему",
@ -347,7 +346,6 @@
"exportFileFail": "Помилка експорту файлу!",
"export": "Експорт"
},
"user": {
"name": "Ім'я",
"email": "Електронна пошта",
@ -416,13 +414,9 @@
"isComplete": "є завершено",
"isIncomplted": "є незавершено"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "є",
"isNot": "не є",
"isEmpty": "порожнє",
"isNotEmpty": "не порожнє"
},
"multiSelectOptionFilter": {
"contains": "Містить",
"doesNotContain": "Не містить",
"isEmpty": "порожнє",

View File

@ -391,7 +391,7 @@
"isComplete": "مکمل ہے",
"isIncomplted": "نامکمل ہے"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "ہے",
"isNot": "نہیں ہے",
"isEmpty": "خالی ہے",

View File

@ -491,13 +491,9 @@
"isComplete": "hoàn tất",
"isIncomplted": "chưa hoàn tất"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "Là",
"isNot": "Không phải",
"isEmpty": "Rỗng",
"isNotEmpty": "Không rỗng"
},
"multiSelectOptionFilter": {
"contains": "Chứa",
"doesNotContain": "Không chứa",
"isEmpty": "Rỗng",
@ -811,4 +807,4 @@
"font": "Phông chữ",
"date": "Ngày"
}
}
}

View File

@ -531,13 +531,9 @@
"isComplete": "已完成",
"isIncomplted": "未完成"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "是",
"isNot": "不是",
"isEmpty": "为空",
"isNotEmpty": "不为空"
},
"multiSelectOptionFilter": {
"contains": "包含",
"doesNotContain": "不包含",
"isEmpty": "为空",

View File

@ -513,13 +513,9 @@
"isComplete": "已完成",
"isIncomplted": "未完成"
},
"singleSelectOptionFilter": {
"selectOptionFilter": {
"is": "是",
"isNot": "不是",
"isEmpty": "為空",
"isNotEmpty": "不為空"
},
"multiSelectOptionFilter": {
"contains": "包含",
"doesNotContain": "不包含",
"isEmpty": "為空",
@ -1261,4 +1257,4 @@
"userIcon": "使用者圖示"
},
"noLogFiles": "這裡沒有日誌記錄檔案"
}
}

View File

@ -8,38 +8,41 @@ use crate::services::{field::SelectOptionIds, filter::ParseFilterData};
#[derive(Eq, PartialEq, ProtoBuf, Debug, Default, Clone)]
pub struct SelectOptionFilterPB {
#[pb(index = 1)]
pub condition: SelectOptionConditionPB,
pub condition: SelectOptionFilterConditionPB,
#[pb(index = 2)]
pub option_ids: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[derive(Debug, Default, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
#[derive(Default)]
pub enum SelectOptionConditionPB {
pub enum SelectOptionFilterConditionPB {
#[default]
OptionIs = 0,
OptionIsNot = 1,
OptionIsEmpty = 2,
OptionIsNotEmpty = 3,
OptionContains = 2,
OptionDoesNotContain = 3,
OptionIsEmpty = 4,
OptionIsNotEmpty = 5,
}
impl std::convert::From<SelectOptionConditionPB> for u32 {
fn from(value: SelectOptionConditionPB) -> Self {
impl From<SelectOptionFilterConditionPB> for u32 {
fn from(value: SelectOptionFilterConditionPB) -> Self {
value as u32
}
}
impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
impl TryFrom<u8> for SelectOptionFilterConditionPB {
type Error = ErrorCode;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(SelectOptionConditionPB::OptionIs),
1 => Ok(SelectOptionConditionPB::OptionIsNot),
2 => Ok(SelectOptionConditionPB::OptionIsEmpty),
3 => Ok(SelectOptionConditionPB::OptionIsNotEmpty),
0 => Ok(SelectOptionFilterConditionPB::OptionIs),
1 => Ok(SelectOptionFilterConditionPB::OptionIsNot),
2 => Ok(SelectOptionFilterConditionPB::OptionContains),
3 => Ok(SelectOptionFilterConditionPB::OptionDoesNotContain),
4 => Ok(SelectOptionFilterConditionPB::OptionIsEmpty),
5 => Ok(SelectOptionFilterConditionPB::OptionIsNotEmpty),
_ => Err(ErrorCode::InvalidParams),
}
}
@ -47,8 +50,8 @@ impl std::convert::TryFrom<u8> for SelectOptionConditionPB {
impl ParseFilterData for SelectOptionFilterPB {
fn parse(condition: u8, content: String) -> Self {
Self {
condition: SelectOptionConditionPB::try_from(condition)
.unwrap_or(SelectOptionConditionPB::OptionIs),
condition: SelectOptionFilterConditionPB::try_from(condition)
.unwrap_or(SelectOptionFilterConditionPB::OptionIs),
option_ids: SelectOptionIds::from_str(&content)
.unwrap_or_default()
.into_inner(),

View File

@ -12,17 +12,16 @@ pub struct TextFilterPB {
pub content: String,
}
#[derive(Debug, Clone, PartialEq, Eq, ProtoBuf_Enum)]
#[derive(Debug, Clone, Default, PartialEq, Eq, ProtoBuf_Enum)]
#[repr(u8)]
#[derive(Default)]
pub enum TextFilterConditionPB {
#[default]
Is = 0,
IsNot = 1,
Contains = 2,
DoesNotContain = 3,
StartsWith = 4,
EndsWith = 5,
TextIs = 0,
TextIsNot = 1,
TextContains = 2,
TextDoesNotContain = 3,
TextStartsWith = 4,
TextEndsWith = 5,
TextIsEmpty = 6,
TextIsNotEmpty = 7,
}
@ -38,12 +37,12 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(TextFilterConditionPB::Is),
1 => Ok(TextFilterConditionPB::IsNot),
2 => Ok(TextFilterConditionPB::Contains),
3 => Ok(TextFilterConditionPB::DoesNotContain),
4 => Ok(TextFilterConditionPB::StartsWith),
5 => Ok(TextFilterConditionPB::EndsWith),
0 => Ok(TextFilterConditionPB::TextIs),
1 => Ok(TextFilterConditionPB::TextIsNot),
2 => Ok(TextFilterConditionPB::TextContains),
3 => Ok(TextFilterConditionPB::TextDoesNotContain),
4 => Ok(TextFilterConditionPB::TextStartsWith),
5 => Ok(TextFilterConditionPB::TextEndsWith),
6 => Ok(TextFilterConditionPB::TextIsEmpty),
7 => Ok(TextFilterConditionPB::TextIsNotEmpty),
_ => Err(ErrorCode::InvalidParams),
@ -54,7 +53,8 @@ impl std::convert::TryFrom<u8> for TextFilterConditionPB {
impl ParseFilterData for TextFilterPB {
fn parse(condition: u8, content: String) -> Self {
Self {
condition: TextFilterConditionPB::try_from(condition).unwrap_or(TextFilterConditionPB::Is),
condition: TextFilterConditionPB::try_from(condition)
.unwrap_or(TextFilterConditionPB::TextIs),
content,
}
}

View File

@ -1,23 +1,26 @@
use std::str::FromStr;
use rust_decimal::Decimal;
use crate::entities::{NumberFilterConditionPB, NumberFilterPB};
use crate::services::field::NumberCellFormat;
use rust_decimal::prelude::Zero;
use rust_decimal::Decimal;
use std::str::FromStr;
impl NumberFilterPB {
pub fn is_visible(&self, cell_data: &NumberCellFormat) -> Option<bool> {
let expected_decimal = Decimal::from_str(&self.content).unwrap_or_else(|_| Decimal::zero());
let expected_decimal = || Decimal::from_str(&self.content).ok();
let strategy = match self.condition {
NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal),
NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal),
NumberFilterConditionPB::GreaterThan => NumberFilterStrategy::GreaterThan(expected_decimal),
NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal),
NumberFilterConditionPB::Equal => NumberFilterStrategy::Equal(expected_decimal()?),
NumberFilterConditionPB::NotEqual => NumberFilterStrategy::NotEqual(expected_decimal()?),
NumberFilterConditionPB::GreaterThan => {
NumberFilterStrategy::GreaterThan(expected_decimal()?)
},
NumberFilterConditionPB::LessThan => NumberFilterStrategy::LessThan(expected_decimal()?),
NumberFilterConditionPB::GreaterThanOrEqualTo => {
NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal)
NumberFilterStrategy::GreaterThanOrEqualTo(expected_decimal()?)
},
NumberFilterConditionPB::LessThanOrEqualTo => {
NumberFilterStrategy::LessThanOrEqualTo(expected_decimal)
NumberFilterStrategy::LessThanOrEqualTo(expected_decimal()?)
},
NumberFilterConditionPB::NumberIsEmpty => NumberFilterStrategy::Empty,
NumberFilterConditionPB::NumberIsNotEmpty => NumberFilterStrategy::NotEmpty,

View File

@ -128,7 +128,7 @@ impl TypeOptionCellDataFilter for MultiSelectTypeOption {
cell_data: &<Self as TypeOption>::CellData,
) -> bool {
let selected_options = self.get_selected_options(cell_data.clone()).select_options;
filter.is_visible(&selected_options, FieldType::MultiSelect)
filter.is_visible(&selected_options).unwrap_or(true)
}
}

View File

@ -1,106 +1,110 @@
#![allow(clippy::needless_collect)]
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use crate::entities::{SelectOptionFilterConditionPB, SelectOptionFilterPB};
use crate::services::field::SelectOption;
impl SelectOptionFilterPB {
pub fn is_visible(&self, selected_options: &[SelectOption], field_type: FieldType) -> bool {
let selected_option_ids: Vec<&String> =
selected_options.iter().map(|option| &option.id).collect();
match self.condition {
SelectOptionConditionPB::OptionIs => match field_type {
FieldType::SingleSelect => {
if self.option_ids.is_empty() {
return true;
}
pub fn is_visible(&self, selected_options: &[SelectOption]) -> Option<bool> {
let selected_option_ids = selected_options
.iter()
.map(|option| &option.id)
.collect::<Vec<_>>();
if selected_options.is_empty() {
return false;
}
let get_non_empty_expected_options =
|| (!self.option_ids.is_empty()).then(|| self.option_ids.clone());
let required_options = self
.option_ids
.iter()
.filter(|id| selected_option_ids.contains(id))
.collect::<Vec<_>>();
!required_options.is_empty()
},
FieldType::MultiSelect => {
if self.option_ids.is_empty() {
return true;
}
let required_options = self
.option_ids
.iter()
.filter(|id| selected_option_ids.contains(id))
.collect::<Vec<_>>();
!required_options.is_empty()
},
_ => false,
let strategy = match self.condition {
SelectOptionFilterConditionPB::OptionIs => {
SelectOptionFilterStrategy::Is(get_non_empty_expected_options()?)
},
SelectOptionConditionPB::OptionIsNot => match field_type {
FieldType::SingleSelect => {
if self.option_ids.is_empty() {
return true;
}
if selected_options.is_empty() {
return false;
}
let required_options = self
.option_ids
.iter()
.filter(|id| selected_option_ids.contains(id))
.collect::<Vec<_>>();
required_options.is_empty()
},
FieldType::MultiSelect => {
let required_options = self
.option_ids
.iter()
.filter(|id| selected_option_ids.contains(id))
.collect::<Vec<_>>();
required_options.is_empty()
},
_ => false,
SelectOptionFilterConditionPB::OptionIsNot => {
SelectOptionFilterStrategy::IsNot(get_non_empty_expected_options()?)
},
SelectOptionConditionPB::OptionIsEmpty => selected_option_ids.is_empty(),
SelectOptionConditionPB::OptionIsNotEmpty => !selected_option_ids.is_empty(),
SelectOptionFilterConditionPB::OptionContains => {
SelectOptionFilterStrategy::Contains(get_non_empty_expected_options()?)
},
SelectOptionFilterConditionPB::OptionDoesNotContain => {
SelectOptionFilterStrategy::DoesNotContain(get_non_empty_expected_options()?)
},
SelectOptionFilterConditionPB::OptionIsEmpty => SelectOptionFilterStrategy::IsEmpty,
SelectOptionFilterConditionPB::OptionIsNotEmpty => SelectOptionFilterStrategy::IsNotEmpty,
};
Some(strategy.filter(&selected_option_ids))
}
}
enum SelectOptionFilterStrategy {
Is(Vec<String>),
IsNot(Vec<String>),
Contains(Vec<String>),
DoesNotContain(Vec<String>),
IsEmpty,
IsNotEmpty,
}
impl SelectOptionFilterStrategy {
fn filter(self, selected_option_ids: &[&String]) -> bool {
match self {
SelectOptionFilterStrategy::Is(option_ids) => {
if selected_option_ids.is_empty() {
return false;
}
selected_option_ids.len() == option_ids.len()
&& selected_option_ids.iter().all(|id| option_ids.contains(id))
},
SelectOptionFilterStrategy::IsNot(option_ids) => {
if selected_option_ids.is_empty() {
return true;
}
selected_option_ids.len() != option_ids.len()
|| !selected_option_ids.iter().all(|id| option_ids.contains(id))
},
SelectOptionFilterStrategy::Contains(option_ids) => {
if selected_option_ids.is_empty() {
return false;
}
let required_options = option_ids
.into_iter()
.filter(|id| selected_option_ids.contains(&id))
.collect::<Vec<_>>();
!required_options.is_empty()
},
SelectOptionFilterStrategy::DoesNotContain(option_ids) => {
if selected_option_ids.is_empty() {
return true;
}
let required_options = option_ids
.into_iter()
.filter(|id| selected_option_ids.contains(&id))
.collect::<Vec<_>>();
required_options.is_empty()
},
SelectOptionFilterStrategy::IsEmpty => selected_option_ids.is_empty(),
SelectOptionFilterStrategy::IsNotEmpty => !selected_option_ids.is_empty(),
}
}
}
#[cfg(test)]
mod tests {
#![allow(clippy::all)]
use crate::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use crate::entities::{SelectOptionFilterConditionPB, SelectOptionFilterPB};
use crate::services::field::SelectOption;
#[test]
fn select_option_filter_is_empty_test() {
let option = SelectOption::new("A");
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsEmpty,
condition: SelectOptionFilterConditionPB::OptionIsEmpty,
option_ids: vec![],
};
assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), true);
assert_eq!(
filter.is_visible(&vec![option.clone()], FieldType::SingleSelect),
false,
);
assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), true);
assert_eq!(
filter.is_visible(&vec![option], FieldType::MultiSelect),
false,
);
assert_eq!(filter.is_visible(&[]), Some(true));
assert_eq!(filter.is_visible(&[option.clone()]), Some(false));
}
#[test]
@ -108,157 +112,227 @@ mod tests {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsNotEmpty,
condition: SelectOptionFilterConditionPB::OptionIsNotEmpty,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
assert_eq!(
filter.is_visible(&vec![option_1.clone()], FieldType::SingleSelect),
true
);
assert_eq!(filter.is_visible(&vec![], FieldType::SingleSelect), false,);
assert_eq!(
filter.is_visible(&vec![option_1.clone()], FieldType::MultiSelect),
true
);
assert_eq!(filter.is_visible(&vec![], FieldType::MultiSelect), false,);
assert_eq!(filter.is_visible(&[]), Some(false));
assert_eq!(filter.is_visible(&[option_1.clone()]), Some(true));
}
#[test]
fn single_select_option_filter_is_not_test() {
fn select_option_filter_is_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("C");
// no expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsNot,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![],
};
for (options, is_visible) in vec![
(vec![option_2.clone()], false),
(vec![option_1.clone()], false),
(vec![option_3.clone()], true),
(vec![option_1.clone(), option_2.clone()], false),
for (options, is_visible) in [
(vec![], None),
(vec![option_1.clone()], None),
(vec![option_1.clone(), option_2.clone()], None),
] {
assert_eq!(
filter.is_visible(&options, FieldType::SingleSelect),
is_visible
);
assert_eq!(filter.is_visible(&options), is_visible);
}
}
#[test]
fn single_select_option_filter_is_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("c");
// one expected option
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![option_1.id.clone()],
};
for (options, is_visible) in vec![
(vec![option_1.clone()], true),
(vec![option_2.clone()], false),
(vec![option_3.clone()], false),
(vec![option_1.clone(), option_2.clone()], true),
for (options, is_visible) in [
(vec![], Some(false)),
(vec![option_1.clone()], Some(true)),
(vec![option_2.clone()], Some(false)),
(vec![option_3.clone()], Some(false)),
(vec![option_1.clone(), option_2.clone()], Some(false)),
] {
assert_eq!(
filter.is_visible(&options, FieldType::SingleSelect),
is_visible
);
assert_eq!(filter.is_visible(&options), is_visible);
}
}
#[test]
fn single_select_option_filter_is_test2() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
// multiple expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
option_ids: vec![],
};
for (options, is_visible) in vec![
(vec![option_1.clone()], true),
(vec![option_2.clone()], true),
(vec![option_1.clone(), option_2.clone()], true),
] {
assert_eq!(
filter.is_visible(&options, FieldType::SingleSelect),
is_visible
);
}
}
#[test]
fn multi_select_option_filter_not_contains_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("C");
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsNot,
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
for (options, is_visible) in vec![
(vec![option_1.clone(), option_2.clone()], false),
(vec![option_1.clone()], false),
(vec![option_2.clone()], false),
(vec![option_3.clone()], true),
for (options, is_visible) in [
(vec![], Some(false)),
(vec![option_1.clone()], Some(false)),
(vec![option_1.clone(), option_2.clone()], Some(true)),
(
vec![option_1.clone(), option_2.clone(), option_3.clone()],
false,
Some(false),
),
(vec![], true),
] {
assert_eq!(
filter.is_visible(&options, FieldType::MultiSelect),
is_visible
);
assert_eq!(filter.is_visible(&options), is_visible);
}
}
#[test]
fn multi_select_option_filter_contains_test() {
fn select_option_filter_is_not_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("C");
// no expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionIsNot,
option_ids: vec![],
};
for (options, is_visible) in [
(vec![], None),
(vec![option_1.clone()], None),
(vec![option_1.clone(), option_2.clone()], None),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
// one expected option
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionIsNot,
option_ids: vec![option_1.id.clone()],
};
for (options, is_visible) in [
(vec![], Some(true)),
(vec![option_1.clone()], Some(false)),
(vec![option_2.clone()], Some(true)),
(vec![option_3.clone()], Some(true)),
(vec![option_1.clone(), option_2.clone()], Some(true)),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
// multiple expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionIsNot,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
for (options, is_visible) in vec![
for (options, is_visible) in [
(vec![], Some(true)),
(vec![option_1.clone()], Some(true)),
(vec![option_1.clone(), option_2.clone()], Some(false)),
(
vec![option_1.clone(), option_2.clone(), option_3.clone()],
true,
Some(true),
),
(vec![option_2.clone(), option_1.clone()], true),
(vec![option_2.clone()], true),
(vec![option_1.clone(), option_3.clone()], true),
(vec![option_3.clone()], false),
] {
assert_eq!(
filter.is_visible(&options, FieldType::MultiSelect),
is_visible
);
assert_eq!(filter.is_visible(&options), is_visible);
}
}
#[test]
fn multi_select_option_filter_contains_test2() {
fn select_option_filter_contains_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("C");
let option_4 = SelectOption::new("D");
// no expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionContains,
option_ids: vec![],
};
for (options, is_visible) in vec![(vec![option_1.clone()], true), (vec![], true)] {
assert_eq!(
filter.is_visible(&options, FieldType::MultiSelect),
is_visible
);
for (options, is_visible) in [
(vec![], None),
(vec![option_1.clone()], None),
(vec![option_1.clone(), option_2.clone()], None),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
// one expected option
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionContains,
option_ids: vec![option_1.id.clone()],
};
for (options, is_visible) in [
(vec![], Some(false)),
(vec![option_1.clone()], Some(true)),
(vec![option_2.clone()], Some(false)),
(vec![option_1.clone(), option_2.clone()], Some(true)),
(vec![option_3.clone(), option_4.clone()], Some(false)),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
// multiple expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionContains,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
for (options, is_visible) in [
(vec![], Some(false)),
(vec![option_1.clone()], Some(true)),
(vec![option_3.clone()], Some(false)),
(vec![option_1.clone(), option_2.clone()], Some(true)),
(vec![option_1.clone(), option_3.clone()], Some(true)),
(vec![option_3.clone(), option_4.clone()], Some(false)),
(
vec![option_1.clone(), option_3.clone(), option_4.clone()],
Some(true),
),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
}
#[test]
fn select_option_filter_does_not_contain_test() {
let option_1 = SelectOption::new("A");
let option_2 = SelectOption::new("B");
let option_3 = SelectOption::new("C");
let option_4 = SelectOption::new("D");
// no expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionDoesNotContain,
option_ids: vec![],
};
for (options, is_visible) in [
(vec![], None),
(vec![option_1.clone()], None),
(vec![option_1.clone(), option_2.clone()], None),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
// one expected option
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionDoesNotContain,
option_ids: vec![option_1.id.clone()],
};
for (options, is_visible) in [
(vec![], Some(true)),
(vec![option_1.clone()], Some(false)),
(vec![option_2.clone()], Some(true)),
(vec![option_1.clone(), option_2.clone()], Some(false)),
(vec![option_3.clone(), option_4.clone()], Some(true)),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
// multiple expected options
let filter = SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionDoesNotContain,
option_ids: vec![option_1.id.clone(), option_2.id.clone()],
};
for (options, is_visible) in [
(vec![], Some(true)),
(vec![option_1.clone()], Some(false)),
(vec![option_3.clone()], Some(true)),
(vec![option_1.clone(), option_2.clone()], Some(false)),
(vec![option_1.clone(), option_3.clone()], Some(false)),
(vec![option_3.clone(), option_4.clone()], Some(true)),
(
vec![option_1.clone(), option_3.clone(), option_4.clone()],
Some(false),
),
] {
assert_eq!(filter.is_visible(&options), is_visible);
}
}
}

View File

@ -119,7 +119,7 @@ impl TypeOptionCellDataFilter for SingleSelectTypeOption {
cell_data: &<Self as TypeOption>::CellData,
) -> bool {
let selected_options = self.get_selected_options(cell_data.clone()).select_options;
filter.is_visible(&selected_options, FieldType::SingleSelect)
filter.is_visible(&selected_options).unwrap_or(true)
}
}

View File

@ -5,12 +5,12 @@ impl TextFilterPB {
let cell_data = cell_data.as_ref().to_lowercase();
let content = &self.content.to_lowercase();
match self.condition {
TextFilterConditionPB::Is => &cell_data == content,
TextFilterConditionPB::IsNot => &cell_data != content,
TextFilterConditionPB::Contains => cell_data.contains(content),
TextFilterConditionPB::DoesNotContain => !cell_data.contains(content),
TextFilterConditionPB::StartsWith => cell_data.starts_with(content),
TextFilterConditionPB::EndsWith => cell_data.ends_with(content),
TextFilterConditionPB::TextIs => &cell_data == content,
TextFilterConditionPB::TextIsNot => &cell_data != content,
TextFilterConditionPB::TextContains => cell_data.contains(content),
TextFilterConditionPB::TextDoesNotContain => !cell_data.contains(content),
TextFilterConditionPB::TextStartsWith => cell_data.starts_with(content),
TextFilterConditionPB::TextEndsWith => cell_data.ends_with(content),
TextFilterConditionPB::TextIsEmpty => cell_data.is_empty(),
TextFilterConditionPB::TextIsNotEmpty => !cell_data.is_empty(),
}
@ -25,7 +25,7 @@ mod tests {
#[test]
fn text_filter_equal_test() {
let text_filter = TextFilterPB {
condition: TextFilterConditionPB::Is,
condition: TextFilterConditionPB::TextIs,
content: "appflowy".to_owned(),
};
@ -37,7 +37,7 @@ mod tests {
#[test]
fn text_filter_start_with_test() {
let text_filter = TextFilterPB {
condition: TextFilterConditionPB::StartsWith,
condition: TextFilterConditionPB::TextStartsWith,
content: "appflowy".to_owned(),
};
@ -49,7 +49,7 @@ mod tests {
#[test]
fn text_filter_end_with_test() {
let text_filter = TextFilterPB {
condition: TextFilterConditionPB::EndsWith,
condition: TextFilterConditionPB::TextEndsWith,
content: "appflowy".to_owned(),
};
@ -70,7 +70,7 @@ mod tests {
#[test]
fn text_filter_contain_test() {
let text_filter = TextFilterPB {
condition: TextFilterConditionPB::Contains,
condition: TextFilterConditionPB::TextContains,
content: "appflowy".to_owned(),
};

View File

@ -1,4 +1,4 @@
use flowy_database2::entities::{FieldType, SelectOptionConditionPB, SelectOptionFilterPB};
use flowy_database2::entities::{FieldType, SelectOptionFilterConditionPB, SelectOptionFilterPB};
use lib_infra::box_any::BoxAny;
use crate::database::filter_test::script::FilterScript::*;
@ -12,7 +12,7 @@ async fn grid_filter_multi_select_is_empty_test() {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsEmpty,
condition: SelectOptionFilterConditionPB::OptionIsEmpty,
option_ids: vec![],
}),
changed: None,
@ -30,7 +30,7 @@ async fn grid_filter_multi_select_is_not_empty_test() {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsNotEmpty,
condition: SelectOptionFilterConditionPB::OptionIsNotEmpty,
option_ids: vec![],
}),
changed: None,
@ -50,12 +50,12 @@ async fn grid_filter_multi_select_is_test() {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![options.remove(0).id, options.remove(0).id],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 5 },
AssertNumberOfVisibleRows { expected: 1 },
];
test.run_scripts(scripts).await;
}
@ -70,12 +70,12 @@ async fn grid_filter_multi_select_is_test2() {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![options.remove(1).id],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 4 },
AssertNumberOfVisibleRows { expected: 1 },
];
test.run_scripts(scripts).await;
}
@ -90,7 +90,7 @@ async fn grid_filter_single_select_is_empty_test() {
parent_filter_id: None,
field_type: FieldType::SingleSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIsEmpty,
condition: SelectOptionFilterConditionPB::OptionIsEmpty,
option_ids: vec![],
}),
changed: Some(FilterRowChanged {
@ -115,7 +115,7 @@ async fn grid_filter_single_select_is_test() {
parent_filter_id: None,
field_type: FieldType::SingleSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![options.remove(0).id],
}),
changed: Some(FilterRowChanged {
@ -142,7 +142,7 @@ async fn grid_filter_single_select_is_test2() {
parent_filter_id: None,
field_type: FieldType::SingleSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionConditionPB::OptionIs,
condition: SelectOptionFilterConditionPB::OptionIs,
option_ids: vec![option.id.clone()],
}),
changed: Some(FilterRowChanged {
@ -169,3 +169,43 @@ async fn grid_filter_single_select_is_test2() {
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_filter_multi_select_contains_test() {
let mut test = DatabaseFilterTest::new().await;
let field = test.get_first_field(FieldType::MultiSelect);
let mut options = test.get_multi_select_type_option(&field.id);
let scripts = vec![
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionContains,
option_ids: vec![options.remove(0).id, options.remove(0).id],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 5 },
];
test.run_scripts(scripts).await;
}
#[tokio::test]
async fn grid_filter_multi_select_contains_test2() {
let mut test = DatabaseFilterTest::new().await;
let field = test.get_first_field(FieldType::MultiSelect);
let mut options = test.get_multi_select_type_option(&field.id);
let scripts = vec![
CreateDataFilter {
parent_filter_id: None,
field_type: FieldType::MultiSelect,
data: BoxAny::new(SelectOptionFilterPB {
condition: SelectOptionFilterConditionPB::OptionContains,
option_ids: vec![options.remove(1).id],
}),
changed: None,
},
AssertNumberOfVisibleRows { expected: 4 },
];
test.run_scripts(scripts).await;
}

View File

@ -70,7 +70,7 @@ async fn grid_filter_is_text_test() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::Is,
condition: TextFilterConditionPB::TextIs,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
@ -88,7 +88,7 @@ async fn grid_filter_contain_text_test() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::Contains,
condition: TextFilterConditionPB::TextContains,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
@ -109,7 +109,7 @@ async fn grid_filter_contain_text_test2() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::Contains,
condition: TextFilterConditionPB::TextContains,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
@ -137,7 +137,7 @@ async fn grid_filter_does_not_contain_text_test() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::DoesNotContain,
condition: TextFilterConditionPB::TextDoesNotContain,
content: "AB".to_string(),
}),
changed: Some(FilterRowChanged {
@ -155,7 +155,7 @@ async fn grid_filter_start_with_text_test() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::StartsWith,
condition: TextFilterConditionPB::TextStartsWith,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
@ -174,7 +174,7 @@ async fn grid_filter_ends_with_text_test() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::EndsWith,
condition: TextFilterConditionPB::TextEndsWith,
content: "A".to_string(),
}),
changed: None,
@ -192,7 +192,7 @@ async fn grid_update_text_filter_test() {
parent_filter_id: None,
field_type: FieldType::RichText,
data: BoxAny::new(TextFilterPB {
condition: TextFilterConditionPB::EndsWith,
condition: TextFilterConditionPB::TextEndsWith,
content: "A".to_string(),
}),
changed: Some(FilterRowChanged {
@ -210,7 +210,7 @@ async fn grid_update_text_filter_test() {
let scripts = vec![
UpdateTextFilter {
filter,
condition: TextFilterConditionPB::Is,
condition: TextFilterConditionPB::TextIs,
content: "A".to_string(),
changed: Some(FilterRowChanged {
showing_num_of_rows: 0,