mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-27 14:06:13 +00:00
Merge branch '2727-filter-lookups' into 'develop'
Allow filtering text-based lookups Closes #2727 See merge request baserow/baserow!2472
This commit is contained in:
commit
f931be08a1
17 changed files with 2253 additions and 16 deletions
backend
changelog/entries/unreleased/feature
web-frontend
locales
modules/database
test/unit/database
|
@ -385,6 +385,28 @@ class DatabaseConfig(AppConfig):
|
||||||
view_filter_type_registry.register(UserIsViewFilterType())
|
view_filter_type_registry.register(UserIsViewFilterType())
|
||||||
view_filter_type_registry.register(UserIsNotViewFilterType())
|
view_filter_type_registry.register(UserIsNotViewFilterType())
|
||||||
|
|
||||||
|
from .views.array_view_filters import (
|
||||||
|
HasEmptyValueViewFilterType,
|
||||||
|
HasNotEmptyValueViewFilterType,
|
||||||
|
HasNotValueContainsViewFilterType,
|
||||||
|
HasNotValueContainsWordViewFilterType,
|
||||||
|
HasNotValueEqualViewFilterType,
|
||||||
|
HasValueContainsViewFilterType,
|
||||||
|
HasValueContainsWordViewFilterType,
|
||||||
|
HasValueEqualViewFilterType,
|
||||||
|
HasValueLengthIsLowerThanViewFilterType,
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter_type_registry.register(HasValueEqualViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasNotValueEqualViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasValueContainsViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasNotValueContainsViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasValueContainsWordViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasNotValueContainsWordViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasValueLengthIsLowerThanViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasEmptyValueViewFilterType())
|
||||||
|
view_filter_type_registry.register(HasNotEmptyValueViewFilterType())
|
||||||
|
|
||||||
from .views.view_aggregations import (
|
from .views.view_aggregations import (
|
||||||
AverageViewAggregationType,
|
AverageViewAggregationType,
|
||||||
DecileViewAggregationType,
|
DecileViewAggregationType,
|
||||||
|
|
|
@ -77,6 +77,14 @@ from baserow.contrib.database.api.views.errors import (
|
||||||
)
|
)
|
||||||
from baserow.contrib.database.db.functions import RandomUUID
|
from baserow.contrib.database.db.functions import RandomUUID
|
||||||
from baserow.contrib.database.export_serialized import DatabaseExportSerializedStructure
|
from baserow.contrib.database.export_serialized import DatabaseExportSerializedStructure
|
||||||
|
from baserow.contrib.database.fields.filter_support import (
|
||||||
|
FilterNotSupportedException,
|
||||||
|
HasValueContainsFilterSupport,
|
||||||
|
HasValueContainsWordFilterSupport,
|
||||||
|
HasValueEmptyFilterSupport,
|
||||||
|
HasValueFilterSupport,
|
||||||
|
HasValueLengthIsLowerThanFilterSupport,
|
||||||
|
)
|
||||||
from baserow.contrib.database.formula import (
|
from baserow.contrib.database.formula import (
|
||||||
BASEROW_FORMULA_TYPE_ALLOWED_FIELDS,
|
BASEROW_FORMULA_TYPE_ALLOWED_FIELDS,
|
||||||
BaserowExpression,
|
BaserowExpression,
|
||||||
|
@ -149,6 +157,7 @@ from .expressions import extract_jsonb_array_values_to_single_string
|
||||||
from .field_cache import FieldCache
|
from .field_cache import FieldCache
|
||||||
from .field_filters import (
|
from .field_filters import (
|
||||||
AnnotatedQ,
|
AnnotatedQ,
|
||||||
|
OptionallyAnnotatedQ,
|
||||||
contains_filter,
|
contains_filter,
|
||||||
contains_word_filter,
|
contains_word_filter,
|
||||||
filename_contains_filter,
|
filename_contains_filter,
|
||||||
|
@ -4308,7 +4317,14 @@ class PhoneNumberFieldType(CollationSortMixin, CharFieldMatchingRegexFieldType):
|
||||||
return collate_expression(Value(value))
|
return collate_expression(Value(value))
|
||||||
|
|
||||||
|
|
||||||
class FormulaFieldType(ReadOnlyFieldType):
|
class FormulaFieldType(
|
||||||
|
HasValueEmptyFilterSupport,
|
||||||
|
HasValueFilterSupport,
|
||||||
|
HasValueContainsFilterSupport,
|
||||||
|
HasValueContainsWordFilterSupport,
|
||||||
|
HasValueLengthIsLowerThanFilterSupport,
|
||||||
|
ReadOnlyFieldType,
|
||||||
|
):
|
||||||
type = "formula"
|
type = "formula"
|
||||||
model_class = FormulaField
|
model_class = FormulaField
|
||||||
|
|
||||||
|
@ -4467,6 +4483,83 @@ class FormulaFieldType(ReadOnlyFieldType):
|
||||||
rich_value=rich_value,
|
rich_value=rich_value,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
def get_in_array_empty_query(self, field_name, model_field, field: FormulaField):
|
||||||
|
(
|
||||||
|
field_instance,
|
||||||
|
field_type,
|
||||||
|
) = self._get_field_instance_and_type_from_formula_field(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueEmptyFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_empty_query(
|
||||||
|
field_name, model_field, field_instance
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_is_query(
|
||||||
|
self,
|
||||||
|
field_name: str,
|
||||||
|
value: str,
|
||||||
|
model_field: models.Field,
|
||||||
|
field: FormulaField,
|
||||||
|
) -> Q | OptionallyAnnotatedQ:
|
||||||
|
(
|
||||||
|
field_instance,
|
||||||
|
field_type,
|
||||||
|
) = self._get_field_instance_and_type_from_formula_field(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_is_query(
|
||||||
|
field_name, value, model_field, field_instance
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_contains_query(
|
||||||
|
self, field_name, value, model_field, field: FormulaField
|
||||||
|
):
|
||||||
|
(
|
||||||
|
field_instance,
|
||||||
|
field_type,
|
||||||
|
) = self._get_field_instance_and_type_from_formula_field(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueContainsFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_contains_query(
|
||||||
|
field_name, value, model_field, field_instance
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_contains_word_query(
|
||||||
|
self, field_name, value, model_field, field: FormulaField
|
||||||
|
):
|
||||||
|
(
|
||||||
|
field_instance,
|
||||||
|
field_type,
|
||||||
|
) = self._get_field_instance_and_type_from_formula_field(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueContainsWordFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_contains_word_query(
|
||||||
|
field_name, value, model_field, field_instance
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_length_is_lower_than_query(
|
||||||
|
self, field_name, value, model_field, field: FormulaField
|
||||||
|
):
|
||||||
|
(
|
||||||
|
field_instance,
|
||||||
|
field_type,
|
||||||
|
) = self._get_field_instance_and_type_from_formula_field(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueLengthIsLowerThanFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_length_is_lower_than_query(
|
||||||
|
field_name, value, model_field, field_instance
|
||||||
|
)
|
||||||
|
|
||||||
def contains_query(self, field_name, value, model_field, field: FormulaField):
|
def contains_query(self, field_name, value, model_field, field: FormulaField):
|
||||||
(
|
(
|
||||||
field_instance,
|
field_instance,
|
||||||
|
|
150
backend/src/baserow/contrib/database/fields/filter_support.py
Normal file
150
backend/src/baserow/contrib/database/fields/filter_support.py
Normal file
|
@ -0,0 +1,150 @@
|
||||||
|
import re
|
||||||
|
import typing
|
||||||
|
|
||||||
|
from django.contrib.postgres.fields import JSONField
|
||||||
|
from django.db import models
|
||||||
|
from django.db.models import BooleanField, F, Q, Value
|
||||||
|
|
||||||
|
from baserow.contrib.database.fields.field_filters import (
|
||||||
|
AnnotatedQ,
|
||||||
|
OptionallyAnnotatedQ,
|
||||||
|
)
|
||||||
|
from baserow.contrib.database.formula.expression_generator.django_expressions import (
|
||||||
|
JSONArrayContainsValueExpr,
|
||||||
|
JSONArrayContainsValueLengthLowerThanExpr,
|
||||||
|
JSONArrayContainsValueSimilarToExpr,
|
||||||
|
)
|
||||||
|
|
||||||
|
if typing.TYPE_CHECKING:
|
||||||
|
from baserow.contrib.database.fields.models import Field
|
||||||
|
|
||||||
|
|
||||||
|
class FilterNotSupportedException(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueEmptyFilterSupport:
|
||||||
|
def get_in_array_empty_query(
|
||||||
|
self, field_name: str, model_field: models.Field, field: "Field"
|
||||||
|
) -> OptionallyAnnotatedQ:
|
||||||
|
"""
|
||||||
|
Specifies a Q expression to filter empty values contained in an array.
|
||||||
|
|
||||||
|
:param field_name: The name of the field.
|
||||||
|
:param model_field: The field's actual django field model instance.
|
||||||
|
:param field: The related field's instance.
|
||||||
|
:return: A Q or AnnotatedQ filter given value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
return Q(**{f"{field_name}__contains": Value([{"value": ""}], JSONField())})
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueFilterSupport:
|
||||||
|
def get_in_array_is_query(
|
||||||
|
self, field_name: str, value: str, model_field: models.Field, field: "Field"
|
||||||
|
) -> OptionallyAnnotatedQ:
|
||||||
|
"""
|
||||||
|
Specifies a Q expression to filter exact values contained in an array.
|
||||||
|
|
||||||
|
:param field_name: The name of the field.
|
||||||
|
:param value: The value to check if it is contained in array.
|
||||||
|
:param model_field: The field's actual django field model instance.
|
||||||
|
:param field: The related field's instance.
|
||||||
|
:return: A Q or AnnotatedQ filter given value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return Q()
|
||||||
|
|
||||||
|
return Q(**{f"{field_name}__contains": Value([{"value": value}], JSONField())})
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueContainsFilterSupport:
|
||||||
|
def get_in_array_contains_query(
|
||||||
|
self, field_name: str, value: str, model_field: models.Field, field: "Field"
|
||||||
|
) -> OptionallyAnnotatedQ:
|
||||||
|
"""
|
||||||
|
Specifies a Q expression to filter values in an array that contain a
|
||||||
|
specific value.
|
||||||
|
|
||||||
|
:param field_name: The name of the field.
|
||||||
|
:param value: The value to check if it is contained in array.
|
||||||
|
:param model_field: The field's actual django field model instance.
|
||||||
|
:param field: The related field's instance.
|
||||||
|
:return: A Q or AnnotatedQ filter given value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
if not value:
|
||||||
|
return Q()
|
||||||
|
annotation_query = JSONArrayContainsValueExpr(
|
||||||
|
F(field_name), Value(f"%{value}%"), output_field=BooleanField()
|
||||||
|
)
|
||||||
|
hashed_value = hash(value)
|
||||||
|
return AnnotatedQ(
|
||||||
|
annotation={
|
||||||
|
f"{field_name}_has_value_contains_{hashed_value}": annotation_query
|
||||||
|
},
|
||||||
|
q={f"{field_name}_has_value_contains_{hashed_value}": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueContainsWordFilterSupport:
|
||||||
|
def get_in_array_contains_word_query(
|
||||||
|
self, field_name: str, value: str, model_field: models.Field, field: "Field"
|
||||||
|
) -> OptionallyAnnotatedQ:
|
||||||
|
"""
|
||||||
|
Specifies a Q expression to filter values in an array that contain a
|
||||||
|
specific word.
|
||||||
|
|
||||||
|
:param field_name: The name of the field.
|
||||||
|
:param value: The value to check if it is contained in array.
|
||||||
|
:param model_field: The field's actual django field model instance.
|
||||||
|
:param field: The related field's instance.
|
||||||
|
:return: A Q or AnnotatedQ filter given value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return Q()
|
||||||
|
value = re.escape(value.upper())
|
||||||
|
annotation_query = JSONArrayContainsValueSimilarToExpr(
|
||||||
|
F(field_name), Value(f"%\\m{value}\\M%"), output_field=BooleanField()
|
||||||
|
)
|
||||||
|
hashed_value = hash(value)
|
||||||
|
return AnnotatedQ(
|
||||||
|
annotation={
|
||||||
|
f"{field_name}_has_value_contains_word_{hashed_value}": annotation_query
|
||||||
|
},
|
||||||
|
q={f"{field_name}_has_value_contains_word_{hashed_value}": True},
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueLengthIsLowerThanFilterSupport:
|
||||||
|
def get_in_array_length_is_lower_than_query(
|
||||||
|
self, field_name: str, value: str, model_field: models.Field, field: "Field"
|
||||||
|
) -> OptionallyAnnotatedQ:
|
||||||
|
"""
|
||||||
|
Specifies a Q expression to filter values in an array that has lower
|
||||||
|
than length.
|
||||||
|
|
||||||
|
:param field_name: The name of the field.
|
||||||
|
:param value: The value representing the length to use for the check.
|
||||||
|
:param model_field: The field's actual django field model instance.
|
||||||
|
:param field: The related field's instance.
|
||||||
|
:return: A Q or AnnotatedQ filter given value.
|
||||||
|
"""
|
||||||
|
|
||||||
|
value = value.strip()
|
||||||
|
if not value:
|
||||||
|
return Q()
|
||||||
|
converted_value = int(value)
|
||||||
|
annotation_query = JSONArrayContainsValueLengthLowerThanExpr(
|
||||||
|
F(field_name), Value(converted_value), output_field=BooleanField()
|
||||||
|
)
|
||||||
|
hashed_value = hash(value)
|
||||||
|
return AnnotatedQ(
|
||||||
|
annotation={
|
||||||
|
f"{field_name}_has_value_length_is_lower_than_{hashed_value}": annotation_query
|
||||||
|
},
|
||||||
|
q={f"{field_name}_has_value_length_is_lower_than_{hashed_value}": True},
|
||||||
|
)
|
|
@ -116,18 +116,12 @@ class JSONArray(Func):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class FileNameContainsExpr(Expression):
|
class BaserowFilterExpression(Expression):
|
||||||
# fmt: off
|
"""
|
||||||
template = (
|
Baserow expression that works with field_name and value
|
||||||
f"""
|
to provide expressions for filters. To use, subclass and
|
||||||
EXISTS(
|
define the template.
|
||||||
SELECT attached_files ->> 'visible_name'
|
"""
|
||||||
FROM JSONB_ARRAY_ELEMENTS(%(field_name)s) as attached_files
|
|
||||||
WHERE UPPER(attached_files ->> 'visible_name') LIKE UPPER(%(value)s)
|
|
||||||
)
|
|
||||||
""" # nosec B608
|
|
||||||
)
|
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
def __init__(self, field_name: F, value: Value, output_field: Field):
|
def __init__(self, field_name: F, value: Value, output_field: Field):
|
||||||
super().__init__(output_field=output_field)
|
super().__init__(output_field=output_field)
|
||||||
|
@ -159,3 +153,59 @@ class FileNameContainsExpr(Expression):
|
||||||
"value": sql_value,
|
"value": sql_value,
|
||||||
}
|
}
|
||||||
return template % data, params_value
|
return template % data, params_value
|
||||||
|
|
||||||
|
|
||||||
|
class FileNameContainsExpr(BaserowFilterExpression):
|
||||||
|
# fmt: off
|
||||||
|
template = (
|
||||||
|
f"""
|
||||||
|
EXISTS(
|
||||||
|
SELECT attached_files ->> 'visible_name'
|
||||||
|
FROM JSONB_ARRAY_ELEMENTS(%(field_name)s) as attached_files
|
||||||
|
WHERE UPPER(attached_files ->> 'visible_name') LIKE UPPER(%(value)s)
|
||||||
|
)
|
||||||
|
""" # nosec B608
|
||||||
|
)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
class JSONArrayContainsValueExpr(BaserowFilterExpression):
|
||||||
|
# fmt: off
|
||||||
|
template = (
|
||||||
|
f"""
|
||||||
|
EXISTS(
|
||||||
|
SELECT filtered_field ->> 'value'
|
||||||
|
FROM JSONB_ARRAY_ELEMENTS(%(field_name)s) as filtered_field
|
||||||
|
WHERE UPPER(filtered_field ->> 'value') LIKE UPPER(%(value)s)
|
||||||
|
)
|
||||||
|
""" # nosec B608
|
||||||
|
)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
class JSONArrayContainsValueSimilarToExpr(BaserowFilterExpression):
|
||||||
|
# fmt: off
|
||||||
|
template = (
|
||||||
|
f"""
|
||||||
|
EXISTS(
|
||||||
|
SELECT filtered_field ->> 'value'
|
||||||
|
FROM JSONB_ARRAY_ELEMENTS(%(field_name)s) as filtered_field
|
||||||
|
WHERE UPPER(filtered_field ->> 'value') SIMILAR TO %(value)s
|
||||||
|
)
|
||||||
|
""" # nosec B608 %(value)s
|
||||||
|
)
|
||||||
|
# fmt: on
|
||||||
|
|
||||||
|
|
||||||
|
class JSONArrayContainsValueLengthLowerThanExpr(BaserowFilterExpression):
|
||||||
|
# fmt: off
|
||||||
|
template = (
|
||||||
|
f"""
|
||||||
|
EXISTS(
|
||||||
|
SELECT filtered_field ->> 'value'
|
||||||
|
FROM JSONB_ARRAY_ELEMENTS(%(field_name)s) as filtered_field
|
||||||
|
WHERE LENGTH(filtered_field ->> 'value') < %(value)s
|
||||||
|
)
|
||||||
|
""" # nosec B608 %(value)s
|
||||||
|
)
|
||||||
|
# fmt: on
|
||||||
|
|
|
@ -20,6 +20,14 @@ from baserow.contrib.database.fields.expressions import (
|
||||||
json_extract_path,
|
json_extract_path,
|
||||||
)
|
)
|
||||||
from baserow.contrib.database.fields.field_sortings import OptionallyAnnotatedOrderBy
|
from baserow.contrib.database.fields.field_sortings import OptionallyAnnotatedOrderBy
|
||||||
|
from baserow.contrib.database.fields.filter_support import (
|
||||||
|
FilterNotSupportedException,
|
||||||
|
HasValueContainsFilterSupport,
|
||||||
|
HasValueContainsWordFilterSupport,
|
||||||
|
HasValueEmptyFilterSupport,
|
||||||
|
HasValueFilterSupport,
|
||||||
|
HasValueLengthIsLowerThanFilterSupport,
|
||||||
|
)
|
||||||
from baserow.contrib.database.fields.mixins import get_date_time_format
|
from baserow.contrib.database.fields.mixins import get_date_time_format
|
||||||
from baserow.contrib.database.fields.utils.duration import (
|
from baserow.contrib.database.fields.utils.duration import (
|
||||||
D_H_M_S,
|
D_H_M_S,
|
||||||
|
@ -95,6 +103,11 @@ class BaserowFormulaBaseTextType(BaserowFormulaTypeHasEmptyBaserowExpression):
|
||||||
|
|
||||||
|
|
||||||
class BaserowFormulaTextType(
|
class BaserowFormulaTextType(
|
||||||
|
HasValueEmptyFilterSupport,
|
||||||
|
HasValueFilterSupport,
|
||||||
|
HasValueContainsFilterSupport,
|
||||||
|
HasValueContainsWordFilterSupport,
|
||||||
|
HasValueLengthIsLowerThanFilterSupport,
|
||||||
BaserowFormulaBaseTextType,
|
BaserowFormulaBaseTextType,
|
||||||
BaserowFormulaTypeHasEmptyBaserowExpression,
|
BaserowFormulaTypeHasEmptyBaserowExpression,
|
||||||
BaserowFormulaValidType,
|
BaserowFormulaValidType,
|
||||||
|
@ -961,7 +974,14 @@ class BaserowFormulaSingleFileType(BaserowJSONBObjectBaseType):
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
class BaserowFormulaArrayType(BaserowFormulaValidType):
|
class BaserowFormulaArrayType(
|
||||||
|
HasValueEmptyFilterSupport,
|
||||||
|
HasValueFilterSupport,
|
||||||
|
HasValueContainsFilterSupport,
|
||||||
|
HasValueContainsWordFilterSupport,
|
||||||
|
HasValueLengthIsLowerThanFilterSupport,
|
||||||
|
BaserowFormulaValidType,
|
||||||
|
):
|
||||||
type = "array"
|
type = "array"
|
||||||
user_overridable_formatting_option_fields = [
|
user_overridable_formatting_option_fields = [
|
||||||
"array_formula_type",
|
"array_formula_type",
|
||||||
|
@ -1123,6 +1143,46 @@ class BaserowFormulaArrayType(BaserowFormulaValidType):
|
||||||
def contains_query(self, field_name, value, model_field, field):
|
def contains_query(self, field_name, value, model_field, field):
|
||||||
return Q()
|
return Q()
|
||||||
|
|
||||||
|
def get_in_array_is_query(self, field_name, value, model_field, field):
|
||||||
|
if not isinstance(self.sub_type, HasValueFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return self.sub_type.get_in_array_is_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_empty_query(self, field_name, model_field, field):
|
||||||
|
if not isinstance(self.sub_type, HasValueEmptyFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return self.sub_type.get_in_array_empty_query(field_name, model_field, field)
|
||||||
|
|
||||||
|
def get_in_array_contains_query(self, field_name, value, model_field, field):
|
||||||
|
if not isinstance(self.sub_type, HasValueContainsFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return self.sub_type.get_in_array_contains_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_contains_word_query(self, field_name, value, model_field, field):
|
||||||
|
if not isinstance(self.sub_type, HasValueContainsWordFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return self.sub_type.get_in_array_contains_word_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_in_array_length_is_lower_than_query(
|
||||||
|
self, field_name, value, model_field, field
|
||||||
|
):
|
||||||
|
if not isinstance(self.sub_type, HasValueLengthIsLowerThanFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return self.sub_type.get_in_array_length_is_lower_than_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
|
||||||
def get_alter_column_prepare_old_value(self, connection, from_field, to_field):
|
def get_alter_column_prepare_old_value(self, connection, from_field, to_field):
|
||||||
return "p_in = '';"
|
return "p_in = '';"
|
||||||
|
|
||||||
|
|
172
backend/src/baserow/contrib/database/views/array_view_filters.py
Normal file
172
backend/src/baserow/contrib/database/views/array_view_filters.py
Normal file
|
@ -0,0 +1,172 @@
|
||||||
|
from baserow.contrib.database.fields.field_filters import OptionallyAnnotatedQ
|
||||||
|
from baserow.contrib.database.fields.field_types import FormulaFieldType
|
||||||
|
from baserow.contrib.database.fields.filter_support import (
|
||||||
|
FilterNotSupportedException,
|
||||||
|
HasValueContainsFilterSupport,
|
||||||
|
HasValueContainsWordFilterSupport,
|
||||||
|
HasValueEmptyFilterSupport,
|
||||||
|
HasValueFilterSupport,
|
||||||
|
HasValueLengthIsLowerThanFilterSupport,
|
||||||
|
)
|
||||||
|
from baserow.contrib.database.fields.registries import field_type_registry
|
||||||
|
from baserow.contrib.database.formula import BaserowFormulaTextType
|
||||||
|
|
||||||
|
from .registries import ViewFilterType
|
||||||
|
from .view_filters import NotViewFilterTypeMixin
|
||||||
|
|
||||||
|
|
||||||
|
class HasEmptyValueViewFilterType(ViewFilterType):
|
||||||
|
"""
|
||||||
|
The filter can be used to check for empty condition for
|
||||||
|
items in an array.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = "has_empty_value"
|
||||||
|
compatible_field_types = [
|
||||||
|
FormulaFieldType.compatible_with_formula_types(
|
||||||
|
FormulaFieldType.array_of(BaserowFormulaTextType.type),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_filter(self, field_name, value, model_field, field) -> OptionallyAnnotatedQ:
|
||||||
|
try:
|
||||||
|
field_type = field_type_registry.get_by_model(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueEmptyFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_empty_query(field_name, model_field, field)
|
||||||
|
except Exception:
|
||||||
|
return self.default_filter_on_exception()
|
||||||
|
|
||||||
|
|
||||||
|
class HasNotEmptyValueViewFilterType(
|
||||||
|
NotViewFilterTypeMixin, HasEmptyValueViewFilterType
|
||||||
|
):
|
||||||
|
type = "has_not_empty_value"
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueEqualViewFilterType(ViewFilterType):
|
||||||
|
"""
|
||||||
|
The filter can be used to check for "is" condition for
|
||||||
|
items in an array.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = "has_value_equal"
|
||||||
|
compatible_field_types = [
|
||||||
|
FormulaFieldType.compatible_with_formula_types(
|
||||||
|
FormulaFieldType.array_of(BaserowFormulaTextType.type),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_filter(self, field_name, value, model_field, field) -> OptionallyAnnotatedQ:
|
||||||
|
try:
|
||||||
|
field_type = field_type_registry.get_by_model(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_is_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return self.default_filter_on_exception()
|
||||||
|
|
||||||
|
|
||||||
|
class HasNotValueEqualViewFilterType(
|
||||||
|
NotViewFilterTypeMixin, HasValueEqualViewFilterType
|
||||||
|
):
|
||||||
|
type = "has_not_value_equal"
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueContainsViewFilterType(ViewFilterType):
|
||||||
|
"""
|
||||||
|
The filter can be used to check for "contains" condition for
|
||||||
|
items in an array.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = "has_value_contains"
|
||||||
|
compatible_field_types = [
|
||||||
|
FormulaFieldType.compatible_with_formula_types(
|
||||||
|
FormulaFieldType.array_of(BaserowFormulaTextType.type),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_filter(self, field_name, value, model_field, field) -> OptionallyAnnotatedQ:
|
||||||
|
try:
|
||||||
|
field_type = field_type_registry.get_by_model(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueContainsFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_contains_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return self.default_filter_on_exception()
|
||||||
|
|
||||||
|
|
||||||
|
class HasNotValueContainsViewFilterType(
|
||||||
|
NotViewFilterTypeMixin, HasValueContainsViewFilterType
|
||||||
|
):
|
||||||
|
type = "has_not_value_contains"
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueContainsWordViewFilterType(ViewFilterType):
|
||||||
|
"""
|
||||||
|
The filter can be used to check for "contains word" condition
|
||||||
|
for items in an array.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = "has_value_contains_word"
|
||||||
|
compatible_field_types = [
|
||||||
|
FormulaFieldType.compatible_with_formula_types(
|
||||||
|
FormulaFieldType.array_of(BaserowFormulaTextType.type),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_filter(self, field_name, value, model_field, field) -> OptionallyAnnotatedQ:
|
||||||
|
try:
|
||||||
|
field_type = field_type_registry.get_by_model(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueContainsWordFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_contains_word_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return self.default_filter_on_exception()
|
||||||
|
|
||||||
|
|
||||||
|
class HasNotValueContainsWordViewFilterType(
|
||||||
|
NotViewFilterTypeMixin, HasValueContainsWordViewFilterType
|
||||||
|
):
|
||||||
|
type = "has_not_value_contains_word"
|
||||||
|
|
||||||
|
|
||||||
|
class HasValueLengthIsLowerThanViewFilterType(ViewFilterType):
|
||||||
|
"""
|
||||||
|
The filter can be used to check for "length is lower than" condition
|
||||||
|
for items in an array.
|
||||||
|
"""
|
||||||
|
|
||||||
|
type = "has_value_length_is_lower_than"
|
||||||
|
compatible_field_types = [
|
||||||
|
FormulaFieldType.compatible_with_formula_types(
|
||||||
|
FormulaFieldType.array_of(BaserowFormulaTextType.type),
|
||||||
|
),
|
||||||
|
]
|
||||||
|
|
||||||
|
def get_filter(self, field_name, value, model_field, field) -> OptionallyAnnotatedQ:
|
||||||
|
try:
|
||||||
|
field_type = field_type_registry.get_by_model(field)
|
||||||
|
|
||||||
|
if not isinstance(field_type, HasValueLengthIsLowerThanFilterSupport):
|
||||||
|
raise FilterNotSupportedException()
|
||||||
|
|
||||||
|
return field_type.get_in_array_length_is_lower_than_query(
|
||||||
|
field_name, value, model_field, field
|
||||||
|
)
|
||||||
|
except Exception:
|
||||||
|
return self.default_filter_on_exception()
|
|
@ -0,0 +1,754 @@
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
|
from django.contrib.auth.models import AbstractUser
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from baserow.contrib.database.fields.models import Field, LinkRowField, LookupField
|
||||||
|
from baserow.contrib.database.rows.handler import RowHandler
|
||||||
|
from baserow.contrib.database.table.models import GeneratedTableModel, Table
|
||||||
|
from baserow.contrib.database.views.handler import ViewHandler
|
||||||
|
from baserow.contrib.database.views.models import GridView
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class ArrayFiltersSetup:
|
||||||
|
user: AbstractUser
|
||||||
|
table: Table
|
||||||
|
model: GeneratedTableModel
|
||||||
|
other_table_model: GeneratedTableModel
|
||||||
|
grid_view: GridView
|
||||||
|
link_row_field: LinkRowField
|
||||||
|
lookup_field: LookupField
|
||||||
|
target_field: Field
|
||||||
|
row_handler: RowHandler
|
||||||
|
view_handler: ViewHandler
|
||||||
|
|
||||||
|
|
||||||
|
def text_field_factory(data_fixture, table, user):
|
||||||
|
return data_fixture.create_text_field(name="target", user=user, table=table)
|
||||||
|
|
||||||
|
|
||||||
|
def long_text_field_factory(data_fixture, table, user):
|
||||||
|
return data_fixture.create_long_text_field(name="target", user=user, table=table)
|
||||||
|
|
||||||
|
|
||||||
|
def setup(data_fixture, target_field_factory):
|
||||||
|
user = data_fixture.create_user()
|
||||||
|
database = data_fixture.create_database_application(user=user)
|
||||||
|
table = data_fixture.create_database_table(user=user, database=database)
|
||||||
|
other_table = data_fixture.create_database_table(user=user, database=database)
|
||||||
|
target_field = target_field_factory(data_fixture, other_table, user)
|
||||||
|
link_row_field = data_fixture.create_link_row_field(
|
||||||
|
name="link", table=table, link_row_table=other_table
|
||||||
|
)
|
||||||
|
lookup_field = data_fixture.create_lookup_field(
|
||||||
|
table=table,
|
||||||
|
through_field=link_row_field,
|
||||||
|
target_field=target_field,
|
||||||
|
through_field_name=link_row_field.name,
|
||||||
|
target_field_name=target_field.name,
|
||||||
|
setup_dependencies=False,
|
||||||
|
)
|
||||||
|
grid_view = data_fixture.create_grid_view(table=table)
|
||||||
|
view_handler = ViewHandler()
|
||||||
|
row_handler = RowHandler()
|
||||||
|
model = table.get_model()
|
||||||
|
other_table_model = other_table.get_model()
|
||||||
|
return ArrayFiltersSetup(
|
||||||
|
user=user,
|
||||||
|
table=table,
|
||||||
|
other_table_model=other_table_model,
|
||||||
|
target_field=target_field,
|
||||||
|
row_handler=row_handler,
|
||||||
|
grid_view=grid_view,
|
||||||
|
link_row_field=link_row_field,
|
||||||
|
lookup_field=lookup_field,
|
||||||
|
view_handler=view_handler,
|
||||||
|
model=model,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_empty_value_filter_text_field_types(data_fixture, target_field_factory):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_A = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "A"}
|
||||||
|
)
|
||||||
|
other_row_B = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "B"}
|
||||||
|
)
|
||||||
|
other_row_empty = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": ""}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [
|
||||||
|
other_row_A.id,
|
||||||
|
other_row_empty.id,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_B.id]},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_empty_value",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 1
|
||||||
|
assert row_1.id in ids
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_not_empty_value_filter_text_field_types(
|
||||||
|
data_fixture, target_field_factory
|
||||||
|
):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_A = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "A"}
|
||||||
|
)
|
||||||
|
other_row_B = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "B"}
|
||||||
|
)
|
||||||
|
other_row_empty = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": ""}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [
|
||||||
|
other_row_A.id,
|
||||||
|
other_row_empty.id,
|
||||||
|
]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_B.id]},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_not_empty_value",
|
||||||
|
value="",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_value_equal_filter_text_field_types(data_fixture, target_field_factory):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_A = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "A"}
|
||||||
|
)
|
||||||
|
other_row_B = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "B"}
|
||||||
|
)
|
||||||
|
other_row_C = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "C"}
|
||||||
|
)
|
||||||
|
other_row_a = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "a"}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [other_row_A.id, other_row_B.id]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_a.id]},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [other_row_B.id, other_row_a.id]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_value_equal",
|
||||||
|
value="A",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 1
|
||||||
|
assert row_1.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "a"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "C"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 0
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_not_value_equal_filter_text_field_types(
|
||||||
|
data_fixture, target_field_factory
|
||||||
|
):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_A = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "A"}
|
||||||
|
)
|
||||||
|
other_row_B = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "B"}
|
||||||
|
)
|
||||||
|
other_row_C = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "C"}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [other_row_A.id, other_row_B.id]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_B.id]},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_not_value_equal",
|
||||||
|
value="A",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "a"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 3
|
||||||
|
|
||||||
|
view_filter.value = "C"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 3
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 3
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_value_contains_filter_text_field_types(data_fixture, target_field_factory):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_John_Smith = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "John Smith"}
|
||||||
|
)
|
||||||
|
other_row_Anna_Smith = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "Anna Smith"}
|
||||||
|
)
|
||||||
|
other_row_John_Wick = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "John Wick"}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_John_Smith.id]},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_Anna_Smith.id]},
|
||||||
|
)
|
||||||
|
row_4 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_John_Wick.id]},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_value_contains",
|
||||||
|
value="smith",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_1.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "john"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_1.id in ids
|
||||||
|
assert row_4.id in ids
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 4
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_not_value_contains_filter_text_field_types(
|
||||||
|
data_fixture, target_field_factory
|
||||||
|
):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_John_Smith = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "John Smith"}
|
||||||
|
)
|
||||||
|
other_row_Anna_Smith = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "Anna Smith"}
|
||||||
|
)
|
||||||
|
other_row_John_Wick = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "John Wick"}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_John_Smith.id]},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_Anna_Smith.id]},
|
||||||
|
)
|
||||||
|
row_4 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_John_Wick.id]},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_not_value_contains",
|
||||||
|
value="smith",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_4.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "john"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 4
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_value_contains_word_filter_text_field_types(
|
||||||
|
data_fixture, target_field_factory
|
||||||
|
):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_1 = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "This is a sentence."}
|
||||||
|
)
|
||||||
|
other_row_2 = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "Another Sentence."}
|
||||||
|
)
|
||||||
|
other_row_3 = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": ""}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [other_row_1.id, other_row_3.id]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_3.id]},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_2.id]},
|
||||||
|
)
|
||||||
|
row_4 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_value_contains_word",
|
||||||
|
value="sentence",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_1.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "Sentence"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_1.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 4
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_not_value_contains_word_filter_text_field_types(
|
||||||
|
data_fixture, target_field_factory
|
||||||
|
):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_1 = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "This is a sentence."}
|
||||||
|
)
|
||||||
|
other_row_2 = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "Another Sentence."}
|
||||||
|
)
|
||||||
|
other_row_3 = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": ""}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [other_row_1.id, other_row_3.id]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_3.id]},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_2.id]},
|
||||||
|
)
|
||||||
|
row_4 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_not_value_contains_word",
|
||||||
|
value="sentence",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_4.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "Sentence"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_4.id in ids
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 4
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
"target_field_factory", [text_field_factory, long_text_field_factory]
|
||||||
|
)
|
||||||
|
@pytest.mark.django_db
|
||||||
|
def test_has_value_length_is_lower_than_text_field_types(
|
||||||
|
data_fixture, target_field_factory
|
||||||
|
):
|
||||||
|
test_setup = setup(data_fixture, target_field_factory)
|
||||||
|
|
||||||
|
other_row_10a = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "aaaaaaaaaa"}
|
||||||
|
)
|
||||||
|
other_row_5a = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": "aaaaa"}
|
||||||
|
)
|
||||||
|
other_row_0a = test_setup.other_table_model.objects.create(
|
||||||
|
**{f"field_{test_setup.target_field.id}": ""}
|
||||||
|
)
|
||||||
|
row_1 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_10a.id]},
|
||||||
|
)
|
||||||
|
row_2 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={
|
||||||
|
f"field_{test_setup.link_row_field.id}": [other_row_0a.id, other_row_10a.id]
|
||||||
|
},
|
||||||
|
)
|
||||||
|
row_3 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": [other_row_5a.id]},
|
||||||
|
)
|
||||||
|
row_4 = test_setup.row_handler.create_row(
|
||||||
|
user=test_setup.user,
|
||||||
|
table=test_setup.table,
|
||||||
|
values={f"field_{test_setup.link_row_field.id}": []},
|
||||||
|
)
|
||||||
|
|
||||||
|
view_filter = data_fixture.create_view_filter(
|
||||||
|
view=test_setup.grid_view,
|
||||||
|
field=test_setup.lookup_field,
|
||||||
|
type="has_value_length_is_lower_than",
|
||||||
|
value="10",
|
||||||
|
)
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 2
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "5"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 1
|
||||||
|
assert row_2.id in ids
|
||||||
|
|
||||||
|
view_filter.value = "11"
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 3
|
||||||
|
assert row_1.id in ids
|
||||||
|
assert row_2.id in ids
|
||||||
|
assert row_3.id in ids
|
||||||
|
|
||||||
|
view_filter.value = ""
|
||||||
|
view_filter.save()
|
||||||
|
ids = [
|
||||||
|
r.id
|
||||||
|
for r in test_setup.view_handler.apply_filters(
|
||||||
|
test_setup.grid_view, test_setup.model.objects.all()
|
||||||
|
).all()
|
||||||
|
]
|
||||||
|
assert len(ids) == 4
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"type": "feature",
|
||||||
|
"message": "Add array filters for formula arrays based on text",
|
||||||
|
"issue_number": 2727,
|
||||||
|
"bullet_points": [],
|
||||||
|
"created_at": "2024-07-10"
|
||||||
|
}
|
|
@ -174,6 +174,15 @@
|
||||||
"password": "A write-only field that holds a hashed password. The value will be `null` if not set, or `true` if it has been set. It accepts a string to set it."
|
"password": "A write-only field that holds a hashed password. The value will be `null` if not set, or `true` if it has been set. It accepts a string to set it."
|
||||||
},
|
},
|
||||||
"viewFilter": {
|
"viewFilter": {
|
||||||
|
"hasEmptyValue": "has empty value",
|
||||||
|
"hasNotEmptyValue": "doesn't have empty value",
|
||||||
|
"hasValueEqual": "has value equal",
|
||||||
|
"hasNotValueEqual": "doesn't have value equal",
|
||||||
|
"hasValueContains": "has value contains",
|
||||||
|
"hasNotValueContains": "doesn't have value contains",
|
||||||
|
"hasValueContainsWord": "has value contains word",
|
||||||
|
"hasNotValueContainsWord": "doesn't have value contains word",
|
||||||
|
"hasValueLengthIsLowerThan": "has value length is lower than",
|
||||||
"contains": "contains",
|
"contains": "contains",
|
||||||
"containsNot": "doesn't contain",
|
"containsNot": "doesn't contain",
|
||||||
"containsWord": "contains word",
|
"containsWord": "contains word",
|
||||||
|
|
210
web-frontend/modules/database/arrayViewFilters.js
Normal file
210
web-frontend/modules/database/arrayViewFilters.js
Normal file
|
@ -0,0 +1,210 @@
|
||||||
|
import ViewFilterTypeText from '@baserow/modules/database/components/view/ViewFilterTypeText'
|
||||||
|
import ViewFilterTypeNumber from '@baserow/modules/database/components/view/ViewFilterTypeNumber'
|
||||||
|
import { FormulaFieldType } from '@baserow/modules/database/fieldTypes'
|
||||||
|
import { ViewFilterType } from '@baserow/modules/database/viewFilters'
|
||||||
|
|
||||||
|
export class HasEmptyValueViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_empty_value'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasEmptyValue')
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.getHasEmptyValueFilterFunction(field)(cellValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasNotEmptyValueViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_not_empty_value'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasNotEmptyValue')
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return !fieldType.getHasEmptyValueFilterFunction(field)(cellValue)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasValueEqualViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_value_equal'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasValueEqual')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.hasValueEqualFilter(cellValue, filterValue, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasNotValueEqualViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_not_value_equal'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasNotValueEqual')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.hasNotValueEqualFilter(cellValue, filterValue, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasValueContainsViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_value_contains'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasValueContains')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.hasValueContainsFilter(cellValue, filterValue, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasNotValueContainsViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_not_value_contains'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasNotValueContains')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.hasNotValueContainsFilter(cellValue, filterValue, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasValueContainsWordViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_value_contains_word'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasValueContainsWord')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.hasValueContainsWordFilter(cellValue, filterValue, field)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasNotValueContainsWordViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_not_value_contains_word'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasNotValueContainsWord')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeText
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.hasNotValueContainsWordFilter(
|
||||||
|
cellValue,
|
||||||
|
filterValue,
|
||||||
|
field
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class HasValueLengthIsLowerThanViewFilterType extends ViewFilterType {
|
||||||
|
static getType() {
|
||||||
|
return 'has_value_length_is_lower_than'
|
||||||
|
}
|
||||||
|
|
||||||
|
getName() {
|
||||||
|
const { i18n } = this.app
|
||||||
|
return i18n.t('viewFilter.hasValueLengthIsLowerThan')
|
||||||
|
}
|
||||||
|
|
||||||
|
getInputComponent(field) {
|
||||||
|
return ViewFilterTypeNumber
|
||||||
|
}
|
||||||
|
|
||||||
|
getCompatibleFieldTypes() {
|
||||||
|
return [FormulaFieldType.compatibleWithFormulaTypes('array(text)')]
|
||||||
|
}
|
||||||
|
|
||||||
|
matches(cellValue, filterValue, field, fieldType) {
|
||||||
|
return fieldType.getHasValueLengthIsLowerThanFilterFunction(field)(
|
||||||
|
cellValue,
|
||||||
|
filterValue
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
82
web-frontend/modules/database/fieldFilterCompatibility.js
Normal file
82
web-frontend/modules/database/fieldFilterCompatibility.js
Normal file
|
@ -0,0 +1,82 @@
|
||||||
|
import {
|
||||||
|
genericHasValueEqualFilter,
|
||||||
|
genericHasValueContainsFilter,
|
||||||
|
genericHasValueContainsWordFilter,
|
||||||
|
genericHasEmptyValueFilter,
|
||||||
|
genericHasValueLengthLowerThanFilter,
|
||||||
|
} from '@baserow/modules/database/utils/fieldFilters'
|
||||||
|
|
||||||
|
export function fieldSupportsFilter(fieldType, filterMixin) {
|
||||||
|
for (const [key, value] of Object.entries(filterMixin)) {
|
||||||
|
/* eslint no-prototype-builtins: "off" */
|
||||||
|
if (!fieldType.prototype.hasOwnProperty(key)) {
|
||||||
|
fieldType.prototype[key] = value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasEmptyValueFilterMixin = {
|
||||||
|
getHasEmptyValueFilterFunction(field) {
|
||||||
|
return genericHasEmptyValueFilter
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasValueEqualFilterMixin = {
|
||||||
|
getHasValueEqualFilterFunction(field) {
|
||||||
|
return genericHasValueEqualFilter
|
||||||
|
},
|
||||||
|
hasValueEqualFilter(cellValue, filterValue, field) {
|
||||||
|
return (
|
||||||
|
filterValue === '' ||
|
||||||
|
this.getHasValueEqualFilterFunction(field)(cellValue, filterValue)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hasNotValueEqualFilter(cellValue, filterValue, field) {
|
||||||
|
return (
|
||||||
|
filterValue === '' ||
|
||||||
|
!this.getHasValueEqualFilterFunction(field)(cellValue, filterValue)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasValueContainsFilterMixin = {
|
||||||
|
getHasValueContainsFilterFunction(field) {
|
||||||
|
return genericHasValueContainsFilter
|
||||||
|
},
|
||||||
|
hasValueContainsFilter(cellValue, filterValue, field) {
|
||||||
|
return (
|
||||||
|
filterValue === '' ||
|
||||||
|
this.getHasValueContainsFilterFunction(field)(cellValue, filterValue)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hasNotValueContainsFilter(cellValue, filterValue, field) {
|
||||||
|
return (
|
||||||
|
filterValue === '' ||
|
||||||
|
!this.getHasValueContainsFilterFunction(field)(cellValue, filterValue)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasValueContainsWordFilterMixin = {
|
||||||
|
getHasValueContainsWordFilterFunction(field) {
|
||||||
|
return genericHasValueContainsWordFilter
|
||||||
|
},
|
||||||
|
hasValueContainsWordFilter(cellValue, filterValue, field) {
|
||||||
|
return (
|
||||||
|
filterValue === '' ||
|
||||||
|
this.getHasValueContainsWordFilterFunction(field)(cellValue, filterValue)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
hasNotValueContainsWordFilter(cellValue, filterValue, field) {
|
||||||
|
return (
|
||||||
|
filterValue === '' ||
|
||||||
|
!this.getHasValueContainsWordFilterFunction(field)(cellValue, filterValue)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasValueLengthIsLowerThanFilterMixin = {
|
||||||
|
getHasValueLengthIsLowerThanFilterFunction(field) {
|
||||||
|
return genericHasValueLengthLowerThanFilter
|
||||||
|
},
|
||||||
|
}
|
|
@ -15,7 +15,14 @@ import {
|
||||||
isValidEmail,
|
isValidEmail,
|
||||||
isValidURL,
|
isValidURL,
|
||||||
} from '@baserow/modules/core/utils/string'
|
} from '@baserow/modules/core/utils/string'
|
||||||
|
import {
|
||||||
|
fieldSupportsFilter,
|
||||||
|
hasEmptyValueFilterMixin,
|
||||||
|
hasValueContainsFilterMixin,
|
||||||
|
hasValueEqualFilterMixin,
|
||||||
|
hasValueContainsWordFilterMixin,
|
||||||
|
hasValueLengthIsLowerThanFilterMixin,
|
||||||
|
} from '@baserow/modules/database/fieldFilterCompatibility'
|
||||||
import moment from '@baserow/modules/core/moment'
|
import moment from '@baserow/modules/core/moment'
|
||||||
import guessFormat from 'moment-guess'
|
import guessFormat from 'moment-guess'
|
||||||
import { Registerable } from '@baserow/modules/core/registry'
|
import { Registerable } from '@baserow/modules/core/registry'
|
||||||
|
@ -135,6 +142,12 @@ import FormViewFieldMultipleLinkRow from '@baserow/modules/database/components/v
|
||||||
import FormViewFieldMultipleSelectCheckboxes from '@baserow/modules/database/components/view/form/FormViewFieldMultipleSelectCheckboxes'
|
import FormViewFieldMultipleSelectCheckboxes from '@baserow/modules/database/components/view/form/FormViewFieldMultipleSelectCheckboxes'
|
||||||
import FormViewFieldSingleSelectRadios from '@baserow/modules/database/components/view/form/FormViewFieldSingleSelectRadios'
|
import FormViewFieldSingleSelectRadios from '@baserow/modules/database/components/view/form/FormViewFieldSingleSelectRadios'
|
||||||
|
|
||||||
|
import {
|
||||||
|
BaserowFormulaArrayType,
|
||||||
|
BaserowFormulaCharType,
|
||||||
|
BaserowFormulaTextType,
|
||||||
|
} from '@baserow/modules/database/formula/formulaTypes'
|
||||||
|
|
||||||
import { trueValues } from '@baserow/modules/core/utils/constants'
|
import { trueValues } from '@baserow/modules/core/utils/constants'
|
||||||
import {
|
import {
|
||||||
getDateMomentFormat,
|
getDateMomentFormat,
|
||||||
|
@ -3658,6 +3671,31 @@ export class FormulaFieldType extends FieldType {
|
||||||
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
||||||
return subType.canRepresentFiles(field)
|
return subType.canRepresentFiles(field)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHasEmptyValueFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
||||||
|
return subType.getHasEmptyValueFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueEqualFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
||||||
|
return subType.getHasValueEqualFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueContainsFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
||||||
|
return subType.getHasValueContainsFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueContainsWordFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
||||||
|
return subType.getHasValueContainsWordFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueLengthIsLowerThanFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get('formula_type', field.formula_type)
|
||||||
|
return subType.getHasValueLengthIsLowerThanFilterFunction(field)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CountFieldType extends FormulaFieldType {
|
export class CountFieldType extends FormulaFieldType {
|
||||||
|
@ -4202,3 +4240,37 @@ export class PasswordFieldType extends FieldType {
|
||||||
return RowHistoryFieldPassword
|
return RowHistoryFieldPassword
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fieldSupportsFilter(FormulaFieldType, hasEmptyValueFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaArrayType, hasEmptyValueFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaTextType, hasEmptyValueFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaCharType, hasEmptyValueFilterMixin)
|
||||||
|
|
||||||
|
fieldSupportsFilter(FormulaFieldType, hasValueEqualFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaArrayType, hasValueEqualFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaTextType, hasValueEqualFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaCharType, hasValueEqualFilterMixin)
|
||||||
|
|
||||||
|
fieldSupportsFilter(FormulaFieldType, hasValueContainsFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaArrayType, hasValueContainsFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaTextType, hasValueContainsFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaCharType, hasValueContainsFilterMixin)
|
||||||
|
|
||||||
|
fieldSupportsFilter(FormulaFieldType, hasValueContainsWordFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaArrayType, hasValueContainsWordFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaTextType, hasValueContainsWordFilterMixin)
|
||||||
|
fieldSupportsFilter(BaserowFormulaCharType, hasValueContainsWordFilterMixin)
|
||||||
|
|
||||||
|
fieldSupportsFilter(FormulaFieldType, hasValueLengthIsLowerThanFilterMixin)
|
||||||
|
fieldSupportsFilter(
|
||||||
|
BaserowFormulaArrayType,
|
||||||
|
hasValueLengthIsLowerThanFilterMixin
|
||||||
|
)
|
||||||
|
fieldSupportsFilter(
|
||||||
|
BaserowFormulaTextType,
|
||||||
|
hasValueLengthIsLowerThanFilterMixin
|
||||||
|
)
|
||||||
|
fieldSupportsFilter(
|
||||||
|
BaserowFormulaCharType,
|
||||||
|
hasValueLengthIsLowerThanFilterMixin
|
||||||
|
)
|
||||||
|
|
|
@ -666,6 +666,46 @@ export class BaserowFormulaArrayType extends BaserowFormulaTypeDefinition {
|
||||||
canGroupByInView() {
|
canGroupByInView() {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getHasEmptyValueFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get(
|
||||||
|
'formula_type',
|
||||||
|
field.array_formula_type
|
||||||
|
)
|
||||||
|
return subType.getHasEmptyValueFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueEqualFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get(
|
||||||
|
'formula_type',
|
||||||
|
field.array_formula_type
|
||||||
|
)
|
||||||
|
return subType.getHasValueEqualFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueContainsFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get(
|
||||||
|
'formula_type',
|
||||||
|
field.array_formula_type
|
||||||
|
)
|
||||||
|
return subType.getHasValueContainsFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueContainsWordFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get(
|
||||||
|
'formula_type',
|
||||||
|
field.array_formula_type
|
||||||
|
)
|
||||||
|
return subType.getHasValueContainsWordFilterFunction(field)
|
||||||
|
}
|
||||||
|
|
||||||
|
getHasValueLengthIsLowerThanFilterFunction(field) {
|
||||||
|
const subType = this.app.$registry.get(
|
||||||
|
'formula_type',
|
||||||
|
field.array_formula_type
|
||||||
|
)
|
||||||
|
return subType.getHasValueLengthIsLowerThanFilterFunction(field)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class BaserowFormulaFileType extends BaserowFormulaTypeDefinition {
|
export class BaserowFormulaFileType extends BaserowFormulaTypeDefinition {
|
||||||
|
|
|
@ -95,6 +95,17 @@ import {
|
||||||
DateAfterOrEqualViewFilterType,
|
DateAfterOrEqualViewFilterType,
|
||||||
DateEqualsDayOfMonthViewFilterType,
|
DateEqualsDayOfMonthViewFilterType,
|
||||||
} from '@baserow/modules/database/viewFilters'
|
} from '@baserow/modules/database/viewFilters'
|
||||||
|
import {
|
||||||
|
HasValueEqualViewFilterType,
|
||||||
|
HasEmptyValueViewFilterType,
|
||||||
|
HasNotEmptyValueViewFilterType,
|
||||||
|
HasNotValueEqualViewFilterType,
|
||||||
|
HasValueContainsViewFilterType,
|
||||||
|
HasNotValueContainsViewFilterType,
|
||||||
|
HasValueContainsWordViewFilterType,
|
||||||
|
HasNotValueContainsWordViewFilterType,
|
||||||
|
HasValueLengthIsLowerThanViewFilterType,
|
||||||
|
} from '@baserow/modules/database/arrayViewFilters'
|
||||||
import {
|
import {
|
||||||
CSVImporterType,
|
CSVImporterType,
|
||||||
PasteImporterType,
|
PasteImporterType,
|
||||||
|
@ -427,6 +438,36 @@ export default (context) => {
|
||||||
new DateAfterDaysAgoViewFilterType(context)
|
new DateAfterDaysAgoViewFilterType(context)
|
||||||
)
|
)
|
||||||
// END
|
// END
|
||||||
|
app.$registry.register('viewFilter', new HasEmptyValueViewFilterType(context))
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasNotEmptyValueViewFilterType(context)
|
||||||
|
)
|
||||||
|
app.$registry.register('viewFilter', new HasValueEqualViewFilterType(context))
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasNotValueEqualViewFilterType(context)
|
||||||
|
)
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasValueContainsViewFilterType(context)
|
||||||
|
)
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasNotValueContainsViewFilterType(context)
|
||||||
|
)
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasValueContainsWordViewFilterType(context)
|
||||||
|
)
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasNotValueContainsWordViewFilterType(context)
|
||||||
|
)
|
||||||
|
app.$registry.register(
|
||||||
|
'viewFilter',
|
||||||
|
new HasValueLengthIsLowerThanViewFilterType(context)
|
||||||
|
)
|
||||||
app.$registry.register('viewFilter', new ContainsViewFilterType(context))
|
app.$registry.register('viewFilter', new ContainsViewFilterType(context))
|
||||||
app.$registry.register('viewFilter', new ContainsNotViewFilterType(context))
|
app.$registry.register('viewFilter', new ContainsNotViewFilterType(context))
|
||||||
app.$registry.register('viewFilter', new ContainsWordViewFilterType(context))
|
app.$registry.register('viewFilter', new ContainsWordViewFilterType(context))
|
||||||
|
|
|
@ -49,3 +49,91 @@ export function genericContainsWordFilter(
|
||||||
filterValue = filterValue.replace(/[-[\]{}()*+?.,\\^$|#]/g, '\\$&')
|
filterValue = filterValue.replace(/[-[\]{}()*+?.,\\^$|#]/g, '\\$&')
|
||||||
return humanReadableRowValue.match(new RegExp(`\\b${filterValue}\\b`))
|
return humanReadableRowValue.match(new RegExp(`\\b${filterValue}\\b`))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function genericHasEmptyValueFilter(cellValue, filterValue) {
|
||||||
|
if (!Array.isArray(cellValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < cellValue.length; i++) {
|
||||||
|
const value = cellValue[i].value
|
||||||
|
|
||||||
|
if (value === '') {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genericHasValueEqualFilter(cellValue, filterValue) {
|
||||||
|
if (!Array.isArray(cellValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < cellValue.length; i++) {
|
||||||
|
const value = cellValue[i].value
|
||||||
|
if (value === filterValue) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genericHasValueContainsFilter(cellValue, filterValue) {
|
||||||
|
if (!Array.isArray(cellValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
filterValue = filterValue.toString().toLowerCase().trim()
|
||||||
|
|
||||||
|
for (let i = 0; i < cellValue.length; i++) {
|
||||||
|
const value = cellValue[i].value.toString().toLowerCase().trim()
|
||||||
|
|
||||||
|
if (value.includes(filterValue)) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genericHasValueContainsWordFilter(cellValue, filterValue) {
|
||||||
|
if (!Array.isArray(cellValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
filterValue = filterValue.toString().toLowerCase().trim()
|
||||||
|
filterValue = filterValue.replace(/[-[\]{}()*+?.,\\^$|#]/g, '\\$&')
|
||||||
|
|
||||||
|
for (let i = 0; i < cellValue.length; i++) {
|
||||||
|
if (cellValue[i].value == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const value = cellValue[i].value.toString().toLowerCase().trim()
|
||||||
|
if (value.match(new RegExp(`\\b${filterValue}\\b`))) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function genericHasValueLengthLowerThanFilter(cellValue, filterValue) {
|
||||||
|
if (!Array.isArray(cellValue)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
for (let i = 0; i < cellValue.length; i++) {
|
||||||
|
if (cellValue[i].value == null) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
const valueLength = cellValue[i].value.toString().length
|
||||||
|
if (valueLength < filterValue) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
|
@ -197,7 +197,6 @@ export class EqualViewFilterType extends ViewFilterType {
|
||||||
'uuid',
|
'uuid',
|
||||||
'autonumber',
|
'autonumber',
|
||||||
'duration',
|
'duration',
|
||||||
FormulaFieldType.compatibleWithFormulaTypes('text', 'char', 'number'),
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
388
web-frontend/test/unit/database/arrayViewFiltersMatch.spec.js
Normal file
388
web-frontend/test/unit/database/arrayViewFiltersMatch.spec.js
Normal file
|
@ -0,0 +1,388 @@
|
||||||
|
import { TestApp } from '@baserow/test/helpers/testApp'
|
||||||
|
import {
|
||||||
|
HasValueEqualViewFilterType,
|
||||||
|
HasNotValueEqualViewFilterType,
|
||||||
|
HasValueContainsViewFilterType,
|
||||||
|
HasNotValueContainsViewFilterType,
|
||||||
|
HasValueContainsWordViewFilterType,
|
||||||
|
HasNotValueContainsWordViewFilterType,
|
||||||
|
HasEmptyValueViewFilterType,
|
||||||
|
HasNotEmptyValueViewFilterType,
|
||||||
|
HasValueLengthIsLowerThanViewFilterType,
|
||||||
|
} from '@baserow/modules/database/arrayViewFilters'
|
||||||
|
import { FormulaFieldType } from '@baserow/modules/database/fieldTypes'
|
||||||
|
|
||||||
|
describe('Text-based array view filters', () => {
|
||||||
|
let testApp = null
|
||||||
|
|
||||||
|
beforeAll(() => {
|
||||||
|
testApp = new TestApp()
|
||||||
|
})
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
testApp.afterEach()
|
||||||
|
})
|
||||||
|
|
||||||
|
const hasTextValueEqualCases = [
|
||||||
|
{
|
||||||
|
cellValue: [],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'B' }, { value: 'A' }],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'a' }],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'Aa' }],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const hasValueEqualSupportedFields = [
|
||||||
|
{
|
||||||
|
TestFieldType: FormulaFieldType,
|
||||||
|
formula_type: 'array',
|
||||||
|
array_formula_type: 'text',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe.each(hasValueEqualSupportedFields)(
|
||||||
|
'HasValueEqualViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasTextValueEqualCases)(
|
||||||
|
'filter matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasValueEqualViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
describe.each(hasValueEqualSupportedFields)(
|
||||||
|
'HasNotValueEqualViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasTextValueEqualCases)(
|
||||||
|
'filter not matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasNotValueEqualViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(!testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasValueContainsCases = [
|
||||||
|
{
|
||||||
|
cellValue: [],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'B' }, { value: 'Aa' }],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 't a t' }],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'C' }],
|
||||||
|
filterValue: 'A',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const hasValueContainsSupportedFields = [
|
||||||
|
{
|
||||||
|
TestFieldType: FormulaFieldType,
|
||||||
|
formula_type: 'array',
|
||||||
|
array_formula_type: 'text',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe.each(hasValueContainsSupportedFields)(
|
||||||
|
'HasValueContainsViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasValueContainsCases)(
|
||||||
|
'filter matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasValueContainsViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
describe.each(hasValueContainsSupportedFields)(
|
||||||
|
'HasNotValueContainsViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasValueContainsCases)(
|
||||||
|
'filter not matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasNotValueContainsViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(!testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasValueContainsWordCases = [
|
||||||
|
{
|
||||||
|
cellValue: [],
|
||||||
|
filterValue: 'Word',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: '...Word...' }, { value: 'Some sentence' }],
|
||||||
|
filterValue: 'Word',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'Word' }],
|
||||||
|
filterValue: 'ord',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'Some word in a sentence.' }],
|
||||||
|
filterValue: 'Word',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'Some Word in a sentence.' }],
|
||||||
|
filterValue: 'word',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const hasValueContainsWordSupportedFields = [
|
||||||
|
{
|
||||||
|
TestFieldType: FormulaFieldType,
|
||||||
|
formula_type: 'array',
|
||||||
|
array_formula_type: 'text',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe.each(hasValueContainsWordSupportedFields)(
|
||||||
|
'HasValueContainsWordViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasValueContainsWordCases)(
|
||||||
|
'filter matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasValueContainsWordViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
describe.each(hasValueContainsWordSupportedFields)(
|
||||||
|
'HasNotValueContainsWordViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasValueContainsWordCases)(
|
||||||
|
'filter not matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasNotValueContainsWordViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(!testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasEmptyValueCases = [
|
||||||
|
{
|
||||||
|
cellValue: [],
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'B' }, { value: '' }],
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: '' }],
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'C' }],
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const hasEmptyValueSupportedFields = [
|
||||||
|
{
|
||||||
|
TestFieldType: FormulaFieldType,
|
||||||
|
formula_type: 'array',
|
||||||
|
array_formula_type: 'text',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe.each(hasEmptyValueSupportedFields)(
|
||||||
|
'HasEmptyValueViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasEmptyValueCases)(
|
||||||
|
'filter matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasEmptyValueViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
describe.each(hasEmptyValueSupportedFields)(
|
||||||
|
'HasNotEmptyValueViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasEmptyValueCases)(
|
||||||
|
'filter not matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasNotEmptyValueViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(!testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
const hasLengthLowerThanValueCases = [
|
||||||
|
{
|
||||||
|
cellValue: [],
|
||||||
|
filterValue: '1',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'aaaaa' }, { value: 'aaaaaaaaaa' }],
|
||||||
|
filterValue: '6',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: 'aaaaa' }],
|
||||||
|
filterValue: '5',
|
||||||
|
expected: false,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
cellValue: [{ value: '' }],
|
||||||
|
filterValue: '1',
|
||||||
|
expected: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const hasLengthLowerThanSupportedFields = [
|
||||||
|
{
|
||||||
|
TestFieldType: FormulaFieldType,
|
||||||
|
formula_type: 'array',
|
||||||
|
array_formula_type: 'text',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
describe.each(hasLengthLowerThanSupportedFields)(
|
||||||
|
'HasValueLengthIsLowerThanViewFilterType %j',
|
||||||
|
(field) => {
|
||||||
|
test.each(hasLengthLowerThanValueCases)(
|
||||||
|
'filter matches values %j',
|
||||||
|
(testValues) => {
|
||||||
|
const fieldType = new field.TestFieldType({
|
||||||
|
app: testApp._app,
|
||||||
|
})
|
||||||
|
const result = new HasValueLengthIsLowerThanViewFilterType({
|
||||||
|
app: testApp._app,
|
||||||
|
}).matches(
|
||||||
|
testValues.cellValue,
|
||||||
|
testValues.filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
expect(result).toBe(testValues.expected)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
})
|
Loading…
Add table
Add a link
Reference in a new issue