mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-27 14:06:13 +00:00
3110 formula duration field filtering
This commit is contained in:
parent
b96a21151f
commit
1fa2af432d
11 changed files with 1950 additions and 253 deletions
backend
src/baserow/contrib/database/views
tests/baserow/contrib/database
changelog/entries/unreleased/feature
web-frontend
|
@ -108,6 +108,7 @@ class EqualViewFilterType(ViewFilterType):
|
|||
BaserowFormulaTextType.type,
|
||||
BaserowFormulaCharType.type,
|
||||
BaserowFormulaNumberType.type,
|
||||
BaserowFormulaDurationType.type,
|
||||
BaserowFormulaURLType.type,
|
||||
),
|
||||
]
|
||||
|
@ -369,7 +370,7 @@ class NumericComparisonViewFilterType(ViewFilterType):
|
|||
AutonumberFieldType.type,
|
||||
DurationFieldType.type,
|
||||
FormulaFieldType.compatible_with_formula_types(
|
||||
BaserowFormulaNumberType.type,
|
||||
BaserowFormulaNumberType.type, BaserowFormulaDurationType.type
|
||||
),
|
||||
]
|
||||
|
||||
|
|
|
@ -0,0 +1,783 @@
|
|||
import typing
|
||||
from datetime import timedelta
|
||||
from functools import partial
|
||||
|
||||
import pytest
|
||||
|
||||
from baserow.contrib.database.fields.field_types import FormulaFieldType
|
||||
from baserow.contrib.database.fields.utils.duration import D_H_M_S
|
||||
from tests.baserow.contrib.database.utils import (
|
||||
duration_field_factory,
|
||||
setup_formula_field,
|
||||
text_field_factory,
|
||||
)
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from baserow.test_utils.fixtures import Fixtures
|
||||
|
||||
|
||||
def duration_formula_filter_proc(
|
||||
data_fixture: "Fixtures",
|
||||
duration_format: str,
|
||||
filter_type_name: str,
|
||||
test_value: str,
|
||||
expected_rows: list[int],
|
||||
expected_test_value: None = None,
|
||||
):
|
||||
"""
|
||||
Common duration formula field test procedure. Each test operates on a fixed set of
|
||||
data, where each table row contains a formula field with a predefined value.
|
||||
|
||||
Formula duration field will store calculated duration value 'as is', with
|
||||
raw value's precision regardless selected duration_format.
|
||||
|
||||
The value will be truncated/rounded according to duration_format for display only.
|
||||
|
||||
However, when filtering, a filter value will be truncated/rounded to the format,
|
||||
which may introduce inconsistencies in filtering.
|
||||
|
||||
"""
|
||||
|
||||
formula_text = """field('target')"""
|
||||
t = setup_formula_field(
|
||||
data_fixture,
|
||||
formula_text=formula_text,
|
||||
formula_type="duration",
|
||||
# Data field is a source of values for formula field. In this case we want it
|
||||
# to be at seconds precision, so we can measure filter value rounding effects.
|
||||
data_field_factory=partial(duration_field_factory, duration_format=D_H_M_S),
|
||||
extra_fields=[partial(text_field_factory, name="text_field")],
|
||||
# Duration format for formula field causes filter value to be rounded
|
||||
# Note that field value will remain the same regardless format
|
||||
formula_extra_kwargs={"duration_format": duration_format},
|
||||
)
|
||||
|
||||
assert t.formula_field.formula_type == "duration"
|
||||
t.view_handler.create_filter(
|
||||
t.user,
|
||||
t.grid_view,
|
||||
field=t.formula_field,
|
||||
type_name=filter_type_name,
|
||||
value=test_value,
|
||||
)
|
||||
src_field_name = t.data_source_field.db_column
|
||||
formula_field_name = t.formula_field.db_column
|
||||
refname = t.extra_fields["text_field"].db_column
|
||||
|
||||
rows = [
|
||||
{src_field_name: 3600, refname: "1h"},
|
||||
{src_field_name: 2 * 3600, refname: "2h"},
|
||||
{src_field_name: 3 * 3600, refname: "3h"},
|
||||
{src_field_name: 4 * 3600, refname: "4h"},
|
||||
{src_field_name: 5 * 3600, refname: "5h"},
|
||||
{src_field_name: None, refname: "none"},
|
||||
{src_field_name: 3601, refname: "1h 1s"},
|
||||
{src_field_name: 3599, refname: "1h -1s"},
|
||||
{src_field_name: (3 * 3600) + 1, refname: "3h 1s"},
|
||||
{src_field_name: (3 * 3600) - 1, refname: "3h -1s"},
|
||||
{src_field_name: 59, refname: "59s"},
|
||||
{src_field_name: 61, refname: "1m 1s"},
|
||||
]
|
||||
|
||||
created = t.row_handler.create_rows(
|
||||
user=t.user,
|
||||
table=t.table,
|
||||
rows_values=rows,
|
||||
send_webhook_events=False,
|
||||
send_realtime_update=False,
|
||||
)
|
||||
|
||||
q = t.view_handler.get_queryset(t.grid_view)
|
||||
actual_names = [getattr(r, refname) for r in q]
|
||||
actual_duration_values = [getattr(r, t.data_source_field.db_column) for r in q]
|
||||
actual_formula_values = [getattr(r, t.formula_field.db_column) for r in q]
|
||||
|
||||
if expected_test_value is not None:
|
||||
mfield = FormulaFieldType().get_model_field(t.formula_field)
|
||||
assert t.formula_field.duration_format == duration_format
|
||||
assert (
|
||||
getattr(mfield.expression_field, "duration_format", None) == duration_format
|
||||
)
|
||||
actual_test_value = mfield.get_prep_value(test_value)
|
||||
assert actual_test_value == expected_test_value
|
||||
|
||||
assert len(q) == len(expected_rows)
|
||||
assert set(actual_names) == set(expected_rows)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_type_name,test_value,expected_rows,duration_format,expected_test_value",
|
||||
[
|
||||
(
|
||||
"equal",
|
||||
str(3 * 3600),
|
||||
[
|
||||
"3h",
|
||||
],
|
||||
"h:mm",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
("equal", "3h", ["3h"], "h:mm", timedelta(hours=3)),
|
||||
("equal", str(3 * 3600), ["3h"], "d h mm ss", timedelta(hours=3)),
|
||||
(
|
||||
"equal",
|
||||
str((3 * 3600) + 2),
|
||||
["3h"],
|
||||
"h:mm",
|
||||
timedelta(hours=3),
|
||||
), # rounded to 3h
|
||||
("equal", "3600s", ["1h"], "h:mm", timedelta(hours=1)),
|
||||
("equal", "1:00", ["1h"], "h:mm", timedelta(hours=1)), # 1h
|
||||
("equal", "1:00", [], "h:mm:ss", timedelta(minutes=1)), # 1m
|
||||
("equal", "0:59", ["1h"], "d h", timedelta(hours=1)), # 1h
|
||||
("equal", "0:59", ["59s"], "d h mm ss", timedelta(seconds=59)), # 59s
|
||||
("equal", "3601s", ["1h"], "h:mm", timedelta(hours=1)), # rounded to 1h
|
||||
(
|
||||
"equal",
|
||||
"3601s",
|
||||
["1h 1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(hours=1, seconds=1),
|
||||
), # exact 1h 1s
|
||||
("equal", "1d 20h", [], "d h:mm", timedelta(days=1, hours=20)),
|
||||
("equal", str(3 * 1800), [], "d h mm ss", timedelta(hours=1, minutes=30)),
|
||||
# 1.5h rounded to 2h
|
||||
("equal", str(3 * 1800), ["2h"], "d h", timedelta(hours=2)),
|
||||
("equal", "invalid", [], "d h mm ss", None),
|
||||
(
|
||||
"equal",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
str(3 * 3600),
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"h:mm",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
"3h 2s",
|
||||
# equals 3h due to rounding
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
str(3 * 3600),
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
str(3 * 1800),
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
timedelta(hours=1, minutes=30),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
str(3 * 1800), # 2h due to rounding
|
||||
[
|
||||
"1h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
"1:00", # parsed as 1m
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
timedelta(minutes=1),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
"1:00", # parsed as 1h
|
||||
[
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"h:mm",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
"invalid",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"not_equal",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_duration_formula_equal_value_filter(
|
||||
data_fixture,
|
||||
filter_type_name,
|
||||
test_value,
|
||||
expected_rows,
|
||||
duration_format,
|
||||
expected_test_value,
|
||||
):
|
||||
"""
|
||||
Test equal/not equal filters. Note that due to implementation,
|
||||
filter value will be rounded accordingly to duration format set for the field.
|
||||
|
||||
:param data_fixture:
|
||||
:param filter_type_name:
|
||||
:param test_value:
|
||||
:param expected_rows:
|
||||
:param duration_format:
|
||||
:return:
|
||||
"""
|
||||
|
||||
duration_formula_filter_proc(
|
||||
data_fixture,
|
||||
duration_format,
|
||||
filter_type_name,
|
||||
test_value,
|
||||
expected_rows,
|
||||
expected_test_value,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_type_name,test_value,expected_rows,duration_format,expected_test_value",
|
||||
[
|
||||
(
|
||||
"higher_than",
|
||||
str(3 * 1800),
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"d h mm ss",
|
||||
timedelta(hours=1, minutes=30),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
str(3 * 1800),
|
||||
["3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
str(3600),
|
||||
["2h", "3h", "4h", "5h", "1h 1s", "3h 1s", "3h -1s"],
|
||||
"d h mm",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
str((2 * 3600) - 2),
|
||||
[
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
],
|
||||
"d h mm",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
"1:59:59",
|
||||
["3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
"1:59:59",
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(hours=1, minutes=59, seconds=59),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
str(3600),
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s", "1h 1s"],
|
||||
"d h mm ss",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
str(3600),
|
||||
["2h", "3h", "4h", "5h", "1h 1s", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
"1:01",
|
||||
["2h", "3h", "4h", "5h", "1h 1s", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
"1:01",
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"h:mm",
|
||||
timedelta(hours=1, minutes=1),
|
||||
),
|
||||
# value parsed to 1m
|
||||
(
|
||||
"higher_than",
|
||||
"1:01",
|
||||
["1h", "2h", "3h", "4h", "5h", "1h 1s", "1h -1s", "3h 1s", "3h -1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(minutes=1, seconds=1),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
"1:00",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"1m 1s",
|
||||
],
|
||||
"h:mm:ss",
|
||||
timedelta(minutes=1),
|
||||
),
|
||||
(
|
||||
"higher_than",
|
||||
str(3 * 3600),
|
||||
["4h", "5h", "3h 1s"],
|
||||
"d h",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
("higher_than", str((3 * 3600) + 1801), ["5h"], "d h", timedelta(hours=4)),
|
||||
(
|
||||
"higher_than",
|
||||
str((3 * 3600) + 1801),
|
||||
["4h", "5h"],
|
||||
"h:mm:ss",
|
||||
timedelta(hours=3, minutes=30, seconds=1),
|
||||
),
|
||||
("higher_than", "invalid", [], "d h", None),
|
||||
(
|
||||
"higher_than",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
str(3 * 1800),
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
str(3 * 1800),
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(hours=1, minutes=30),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
str((3 * 3600) + 1),
|
||||
["3h", "4h", "5h", "3h 1s"],
|
||||
"d h",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
str((3 * 3600) + 1),
|
||||
["4h", "5h", "3h 1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(hours=3, seconds=1),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
"1:59:59", # 1h59m59s
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(hours=1, minutes=59, seconds=59),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
"1:59:59", # 1h59m59s
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
"1:59", # parsed as 1m59s
|
||||
["1h", "2h", "3h", "4h", "5h", "1h 1s", "1h -1s", "3h 1s", "3h -1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(minutes=1, seconds=59),
|
||||
),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
"1:59", # parsed as 1h59m, rounded to 2h
|
||||
["2h", "3h", "4h", "5h", "3h 1s", "3h -1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
("higher_than_or_equal", "invalid", [], "d h mm ss", None),
|
||||
(
|
||||
"higher_than_or_equal",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_duration_formula_higher_than_equal_value_filter(
|
||||
data_fixture,
|
||||
filter_type_name,
|
||||
test_value,
|
||||
expected_rows,
|
||||
duration_format,
|
||||
expected_test_value,
|
||||
):
|
||||
duration_formula_filter_proc(
|
||||
data_fixture,
|
||||
duration_format,
|
||||
filter_type_name,
|
||||
test_value,
|
||||
expected_rows,
|
||||
expected_test_value,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_type_name,test_value,expected_rows,duration_format,expected_test_value",
|
||||
[
|
||||
# duration rounding will bump filter value from 1.5h to 2h for `d h` format
|
||||
(
|
||||
"lower_than",
|
||||
str(3 * 1800),
|
||||
["1h", "1h -1s", "1h 1s", "59s", "1m 1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
# filter value is rounded to 1h
|
||||
(
|
||||
"lower_than",
|
||||
str(3599),
|
||||
["1h -1s", "59s", "1m 1s"],
|
||||
"d h",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"lower_than",
|
||||
str(3 * 3600),
|
||||
["1h", "2h", "1h 1s", "1h -1s", "3h -1s", "59s", "1m 1s"],
|
||||
"d h mm ss",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
("lower_than", "invalid", [], "d h mm ss", None),
|
||||
(
|
||||
"lower_than",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
None,
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
"1:01", # parsed as 1m1!
|
||||
["59s", "1m 1s"],
|
||||
"h:mm:ss",
|
||||
timedelta(seconds=61),
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
"1:01", # parsed as 1h1m, but truncated to 1h
|
||||
["1h", "1h -1s", "59s", "1m 1s"],
|
||||
"d h",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
str(3 * 3600),
|
||||
["1h", "2h", "3h", "1h 1s", "1h -1s", "3h -1s", "59s", "1m 1s"],
|
||||
"d h",
|
||||
timedelta(hours=3),
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
str((3 * 3600) - 1801),
|
||||
["1h", "2h", "1h -1s", "1h 1s", "59s", "1m 1s"],
|
||||
"d h",
|
||||
timedelta(hours=2),
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
"1:01", # parsed as 1m1s
|
||||
["1m 1s", "59s"],
|
||||
"h:mm:ss",
|
||||
timedelta(seconds=61),
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
"0:01", # parsed as 1m!
|
||||
["59s"],
|
||||
"h:mm",
|
||||
timedelta(minutes=1),
|
||||
),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
"0:59", # parsed 59m, rounded to 1h
|
||||
["1h", "1h -1s", "59s", "1m 1s"],
|
||||
"d h",
|
||||
timedelta(hours=1),
|
||||
),
|
||||
("lower_than_or_equal", "invalid", [], "d h mm ss", None),
|
||||
(
|
||||
"lower_than_or_equal",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"none",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h mm ss",
|
||||
None,
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_duration_formula_lower_than_equal_value_filter(
|
||||
data_fixture,
|
||||
filter_type_name,
|
||||
test_value,
|
||||
expected_rows,
|
||||
duration_format,
|
||||
expected_test_value,
|
||||
):
|
||||
duration_formula_filter_proc(
|
||||
data_fixture,
|
||||
duration_format,
|
||||
filter_type_name,
|
||||
test_value,
|
||||
expected_rows,
|
||||
expected_test_value,
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"filter_type_name,test_value,expected_rows,duration_format",
|
||||
[
|
||||
("empty", "", ["none"], "d h"),
|
||||
(
|
||||
"not_empty",
|
||||
"",
|
||||
[
|
||||
"1h",
|
||||
"2h",
|
||||
"3h",
|
||||
"4h",
|
||||
"5h",
|
||||
"1h 1s",
|
||||
"1h -1s",
|
||||
"3h 1s",
|
||||
"3h -1s",
|
||||
"59s",
|
||||
"1m 1s",
|
||||
],
|
||||
"d h",
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.django_db
|
||||
def test_duration_formula_empty_value_filter(
|
||||
data_fixture, filter_type_name, test_value, expected_rows, duration_format
|
||||
):
|
||||
duration_formula_filter_proc(
|
||||
data_fixture, duration_format, filter_type_name, test_value, expected_rows
|
||||
)
|
|
@ -1,7 +1,24 @@
|
|||
import asyncio
|
||||
import dataclasses
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Iterable
|
||||
|
||||
from django.contrib.auth.models import AbstractUser
|
||||
|
||||
from channels.testing import WebsocketCommunicator
|
||||
|
||||
from baserow.contrib.database.fields.models import (
|
||||
Field,
|
||||
FormulaField,
|
||||
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
|
||||
from baserow.test_utils.fixtures import Fixtures
|
||||
|
||||
|
||||
async def received_message(communicator: WebsocketCommunicator, message_type: str):
|
||||
"""
|
||||
|
@ -32,3 +49,191 @@ async def get_message(communicator: WebsocketCommunicator, message_type: str):
|
|||
return message
|
||||
except asyncio.exceptions.TimeoutError: # No more messages
|
||||
return None
|
||||
|
||||
|
||||
@dataclass
|
||||
class LookupFieldSetup:
|
||||
user: AbstractUser
|
||||
table: Table
|
||||
other_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
|
||||
|
||||
|
||||
@dataclasses.dataclass
|
||||
class FormulaFieldSetup:
|
||||
user: AbstractUser
|
||||
table: Table
|
||||
formula_field: FormulaField
|
||||
model: GeneratedTableModel
|
||||
grid_view: GridView
|
||||
data_source_field: Field
|
||||
row_handler: RowHandler
|
||||
view_handler: ViewHandler
|
||||
formula: str
|
||||
formula_type: str
|
||||
extra_fields: dict[str, Field]
|
||||
|
||||
|
||||
def boolean_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_boolean_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def text_field_factory(data_fixture, table, user, name: str | None = None):
|
||||
return data_fixture.create_text_field(name=name or "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 url_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_url_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def email_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_email_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def phone_number_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_phone_number_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def uuid_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_uuid_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def single_select_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_single_select_field(
|
||||
name="target", user=user, table=table
|
||||
)
|
||||
|
||||
|
||||
def single_select_field_value_factory(data_fixture, target_field, value=None):
|
||||
return (
|
||||
data_fixture.create_select_option(field=target_field, value=value)
|
||||
if value
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def duration_field_factory(
|
||||
data_fixture, table, user, duration_format: str = "d h mm", name: str | None = None
|
||||
):
|
||||
return data_fixture.create_duration_field(
|
||||
name=name or "target", user=user, table=table, duration_format=duration_format
|
||||
)
|
||||
|
||||
|
||||
def number_field_factory(data_fixture: Fixtures, table, user, **kwargs):
|
||||
return data_fixture.create_number_field(
|
||||
name="target", table=table, user=user, **kwargs
|
||||
)
|
||||
|
||||
|
||||
def text_field_value_factory(data_fixture, target_field, value=None):
|
||||
return value or ""
|
||||
|
||||
|
||||
def setup_linked_table_and_lookup(
|
||||
data_fixture, target_field_factory
|
||||
) -> LookupFieldSetup:
|
||||
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 LookupFieldSetup(
|
||||
user=user,
|
||||
table=table,
|
||||
other_table=other_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,
|
||||
)
|
||||
|
||||
|
||||
def setup_formula_field(
|
||||
data_fixture,
|
||||
formula_text: str,
|
||||
formula_type: str,
|
||||
data_field_factory,
|
||||
extra_fields: Iterable[Callable],
|
||||
formula_extra_kwargs: dict | None = None,
|
||||
) -> FormulaFieldSetup:
|
||||
"""
|
||||
Create a table with duration formula field.
|
||||
|
||||
:param data_fixture:
|
||||
:param formula_text:
|
||||
:param formula_type:
|
||||
:param data_field_factory:
|
||||
:param extra_fields: iterable with field factory functions.
|
||||
:param formula_extra_kwargs: optional dict with additional keyword args for
|
||||
formula field creation
|
||||
:return:
|
||||
"""
|
||||
|
||||
user = data_fixture.create_user()
|
||||
database = data_fixture.create_database_application(user=user)
|
||||
table = data_fixture.create_database_table(user=user, database=database)
|
||||
data_source_field = data_field_factory(data_fixture, table, user)
|
||||
|
||||
formula_field = data_fixture.create_formula_field(
|
||||
table=table,
|
||||
user=user,
|
||||
formula=formula_text,
|
||||
formula_type=formula_type,
|
||||
**{k: v for k, v in (formula_extra_kwargs or {}).items()},
|
||||
)
|
||||
|
||||
extra_fields_map = {}
|
||||
for field_factory in extra_fields:
|
||||
extra_field = field_factory(data_fixture, table=table, user=user)
|
||||
extra_fields_map[extra_field.name] = extra_field
|
||||
|
||||
grid_view = data_fixture.create_grid_view(table=table)
|
||||
view_handler = ViewHandler()
|
||||
row_handler = RowHandler()
|
||||
model = table.get_model()
|
||||
|
||||
return FormulaFieldSetup(
|
||||
user=user,
|
||||
table=table,
|
||||
data_source_field=data_source_field,
|
||||
formula_field=formula_field,
|
||||
row_handler=row_handler,
|
||||
grid_view=grid_view,
|
||||
view_handler=view_handler,
|
||||
model=model,
|
||||
formula=formula_text,
|
||||
formula_type=formula_type,
|
||||
extra_fields=extra_fields_map,
|
||||
)
|
||||
|
|
|
@ -1,36 +1,26 @@
|
|||
import typing
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
||||
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
|
||||
from tests.baserow.contrib.database.utils import (
|
||||
boolean_field_factory,
|
||||
email_field_factory,
|
||||
long_text_field_factory,
|
||||
phone_number_field_factory,
|
||||
setup_linked_table_and_lookup,
|
||||
single_select_field_factory,
|
||||
single_select_field_value_factory,
|
||||
text_field_factory,
|
||||
text_field_value_factory,
|
||||
url_field_factory,
|
||||
uuid_field_factory,
|
||||
)
|
||||
|
||||
if typing.TYPE_CHECKING:
|
||||
from baserow.test_utils.fixtures import Fixtures
|
||||
|
||||
|
||||
@dataclass
|
||||
class ArrayFiltersSetup:
|
||||
user: AbstractUser
|
||||
table: Table
|
||||
other_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
|
||||
|
||||
|
||||
class BooleanLookupRow(int, Enum):
|
||||
"""
|
||||
Helper enum for boolean lookup field filters tests.
|
||||
|
@ -46,85 +36,6 @@ class BooleanLookupRow(int, Enum):
|
|||
NO_VALUES = 3
|
||||
|
||||
|
||||
def boolean_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_boolean_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
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 url_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_url_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def email_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_email_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def phone_number_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_phone_number_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def uuid_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_uuid_field(name="target", user=user, table=table)
|
||||
|
||||
|
||||
def single_select_field_factory(data_fixture, table, user):
|
||||
return data_fixture.create_single_select_field(
|
||||
name="target", user=user, table=table
|
||||
)
|
||||
|
||||
|
||||
def single_select_field_value_factory(data_fixture, target_field, value=None):
|
||||
return (
|
||||
data_fixture.create_select_option(field=target_field, value=value)
|
||||
if value
|
||||
else None
|
||||
)
|
||||
|
||||
|
||||
def setup(data_fixture, target_field_factory) -> ArrayFiltersSetup:
|
||||
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=other_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,
|
||||
)
|
||||
|
||||
|
||||
def boolean_lookup_filter_proc(
|
||||
data_fixture: "Fixtures",
|
||||
filter_type_name: str,
|
||||
|
@ -137,7 +48,7 @@ def boolean_lookup_filter_proc(
|
|||
rows.
|
||||
"""
|
||||
|
||||
test_setup = setup(data_fixture, boolean_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, boolean_field_factory)
|
||||
|
||||
dict_rows = [{test_setup.target_field.db_column: idx % 2} for idx in range(0, 10)]
|
||||
|
||||
|
@ -193,10 +104,6 @@ def boolean_lookup_filter_proc(
|
|||
assert set([r.id for r in q]) == set([r.id for r in selected])
|
||||
|
||||
|
||||
def text_field_value_factory(data_fixture, target_field, value=None):
|
||||
return value or ""
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"target_field_factory,target_field_value_factory",
|
||||
[
|
||||
|
@ -212,7 +119,7 @@ def text_field_value_factory(data_fixture, target_field, value=None):
|
|||
def test_has_empty_value_filter_text_field_types(
|
||||
data_fixture, target_field_factory, target_field_value_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, target_field_factory)
|
||||
|
||||
row_A_value = target_field_value_factory(data_fixture, test_setup.target_field, "A")
|
||||
row_B_value = target_field_value_factory(data_fixture, test_setup.target_field, "B")
|
||||
|
@ -266,7 +173,7 @@ def test_has_empty_value_filter_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_empty_value_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_1 = test_setup.other_table_model.objects.create()
|
||||
other_row_2 = test_setup.other_table_model.objects.create()
|
||||
|
@ -322,7 +229,7 @@ def test_has_empty_value_filter_uuid_field_types(data_fixture):
|
|||
def test_has_not_empty_value_filter_text_field_types(
|
||||
data_fixture, target_field_factory, target_field_value_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, target_field_factory)
|
||||
|
||||
row_A_value = target_field_value_factory(data_fixture, test_setup.target_field, "A")
|
||||
row_B_value = target_field_value_factory(data_fixture, test_setup.target_field, "B")
|
||||
|
@ -377,7 +284,7 @@ def test_has_not_empty_value_filter_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_empty_value_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_1 = test_setup.other_table_model.objects.create()
|
||||
other_row_2 = test_setup.other_table_model.objects.create()
|
||||
|
@ -430,7 +337,7 @@ def test_has_not_empty_value_filter_uuid_field_types(data_fixture):
|
|||
)
|
||||
@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)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, target_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{f"field_{test_setup.target_field.id}": "A"}
|
||||
|
@ -514,7 +421,7 @@ def test_has_value_equal_filter_text_field_types(data_fixture, target_field_fact
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_equal_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{
|
||||
|
@ -615,7 +522,7 @@ def test_has_value_equal_filter_uuid_field_types(data_fixture):
|
|||
def test_has_not_value_equal_filter_text_field_types(
|
||||
data_fixture, target_field_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, target_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{f"field_{test_setup.target_field.id}": "A"}
|
||||
|
@ -693,7 +600,7 @@ def test_has_not_value_equal_filter_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_value_equal_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{
|
||||
|
@ -792,7 +699,7 @@ def test_has_not_value_equal_filter_uuid_field_types(data_fixture):
|
|||
)
|
||||
@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)
|
||||
test_setup = setup_linked_table_and_lookup(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"}
|
||||
|
@ -865,7 +772,7 @@ def test_has_value_contains_filter_text_field_types(data_fixture, target_field_f
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_contains_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{
|
||||
|
@ -978,7 +885,7 @@ def test_has_value_contains_filter_uuid_field_types(data_fixture):
|
|||
def test_has_not_value_contains_filter_text_field_types(
|
||||
data_fixture, target_field_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(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"}
|
||||
|
@ -1051,7 +958,7 @@ def test_has_not_value_contains_filter_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_value_contains_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{
|
||||
|
@ -1163,7 +1070,7 @@ def test_has_not_value_contains_filter_uuid_field_types(data_fixture):
|
|||
def test_has_value_contains_word_filter_text_field_types(
|
||||
data_fixture, target_field_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(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."}
|
||||
|
@ -1238,7 +1145,7 @@ def test_has_value_contains_word_filter_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_contains_word_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{
|
||||
|
@ -1349,7 +1256,7 @@ def test_has_value_contains_word_filter_uuid_field_types(data_fixture):
|
|||
def test_has_not_value_contains_word_filter_text_field_types(
|
||||
data_fixture, target_field_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(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."}
|
||||
|
@ -1424,7 +1331,7 @@ def test_has_not_value_contains_word_filter_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_value_contains_word_filter_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
|
||||
other_row_A = test_setup.other_table_model.objects.create(
|
||||
**{
|
||||
|
@ -1535,7 +1442,7 @@ def test_has_not_value_contains_word_filter_uuid_field_types(data_fixture):
|
|||
def test_has_value_length_is_lower_than_text_field_types(
|
||||
data_fixture, target_field_factory
|
||||
):
|
||||
test_setup = setup(data_fixture, target_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, target_field_factory)
|
||||
|
||||
other_row_10a = test_setup.other_table_model.objects.create(
|
||||
**{f"field_{test_setup.target_field.id}": "aaaaaaaaaa"}
|
||||
|
@ -1622,7 +1529,7 @@ def test_has_value_length_is_lower_than_text_field_types(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_length_is_lower_than_uuid_field_types(data_fixture):
|
||||
test_setup = setup(data_fixture, uuid_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(data_fixture, uuid_field_factory)
|
||||
other_row_1 = test_setup.other_table_model.objects.create()
|
||||
other_row_2 = test_setup.other_table_model.objects.create()
|
||||
row_1 = test_setup.row_handler.create_row(
|
||||
|
@ -1878,7 +1785,9 @@ def test_empty_not_empty_filters_boolean_lookup_field_type(
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_equal_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="b")
|
||||
|
@ -1946,7 +1855,9 @@ def test_has_value_equal_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_value_equal_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="b")
|
||||
|
@ -2013,7 +1924,9 @@ def test_has_not_value_equal_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_contains_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="ba")
|
||||
|
@ -2081,7 +1994,9 @@ def test_has_value_contains_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_value_contains_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="ba")
|
||||
|
@ -2147,7 +2062,9 @@ def test_has_not_value_contains_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_value_contains_word_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="b")
|
||||
|
@ -2214,7 +2131,9 @@ def test_has_value_contains_word_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_not_value_contains_word_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="b")
|
||||
|
@ -2281,7 +2200,9 @@ def test_has_not_value_contains_word_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_any_select_option_equal_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="b")
|
||||
|
@ -2350,7 +2271,9 @@ def test_has_any_select_option_equal_filter_single_select_field(data_fixture):
|
|||
|
||||
@pytest.mark.django_db
|
||||
def test_has_none_select_option_equal_filter_single_select_field(data_fixture):
|
||||
test_setup = setup(data_fixture, single_select_field_factory)
|
||||
test_setup = setup_linked_table_and_lookup(
|
||||
data_fixture, single_select_field_factory
|
||||
)
|
||||
|
||||
opt_a = data_fixture.create_select_option(field=test_setup.target_field, value="a")
|
||||
opt_b = data_fixture.create_select_option(field=test_setup.target_field, value="b")
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "Duration formula field filters",
|
||||
"issue_number": 3110,
|
||||
"bullet_points": [],
|
||||
"created_at": "2024-11-19"
|
||||
}
|
|
@ -20,6 +20,13 @@ import durationField from '@baserow/modules/database/mixins/durationField'
|
|||
export default {
|
||||
name: 'ViewFilterTypeDuration',
|
||||
mixins: [filterTypeInput, durationField],
|
||||
watch: {
|
||||
'field.duration_format': {
|
||||
handler() {
|
||||
this.updateFormattedValue(this.field, this.filter.value)
|
||||
},
|
||||
},
|
||||
},
|
||||
created() {
|
||||
this.updateCopy(this.field, this.filter.value)
|
||||
this.updateFormattedValue(this.field, this.filter.value)
|
||||
|
|
|
@ -165,6 +165,8 @@ import FormService from '@baserow/modules/database/services/view/form'
|
|||
import { UploadFileUserFileUploadType } from '@baserow/modules/core/userFileUploadTypes'
|
||||
import _ from 'lodash'
|
||||
import { trueValues } from '@baserow/modules/core/utils/constants'
|
||||
import ViewFilterTypeNumber from '@baserow/modules/database/components/view/ViewFilterTypeNumber.vue'
|
||||
import ViewFilterTypeDuration from '@baserow/modules/database/components/view/ViewFilterTypeDuration.vue'
|
||||
|
||||
export class FieldType extends Registerable {
|
||||
/**
|
||||
|
@ -1308,6 +1310,10 @@ export class NumberFieldType extends FieldType {
|
|||
return RowHistoryFieldNumber
|
||||
}
|
||||
|
||||
getFilterInputComponent(field, filterType) {
|
||||
return ViewFilterTypeNumber
|
||||
}
|
||||
|
||||
getSortIndicator() {
|
||||
return ['text', '1', '9']
|
||||
}
|
||||
|
@ -2475,6 +2481,10 @@ export class DurationFieldType extends FieldType {
|
|||
return FunctionalGridViewFieldDuration
|
||||
}
|
||||
|
||||
getFilterInputComponent(field, filterType) {
|
||||
return ViewFilterTypeDuration
|
||||
}
|
||||
|
||||
getCanImport() {
|
||||
return true
|
||||
}
|
||||
|
|
|
@ -65,6 +65,7 @@ import {
|
|||
genericHasValueContainsFilter,
|
||||
} from '@baserow/modules/database/utils/fieldFilters'
|
||||
import ViewFilterTypeSelectOptions from '@baserow/modules/database/components/view/ViewFilterTypeSelectOptions.vue'
|
||||
import ViewFilterTypeDuration from '@baserow/modules/database/components/view/ViewFilterTypeDuration.vue'
|
||||
|
||||
export class BaserowFormulaTypeDefinition extends Registerable {
|
||||
getIconClass() {
|
||||
|
@ -457,6 +458,10 @@ export class BaserowFormulaDurationType extends BaserowFormulaTypeDefinition {
|
|||
return RowEditFieldDurationReadOnly
|
||||
}
|
||||
|
||||
getFilterInputComponent(field, filterType) {
|
||||
return ViewFilterTypeDuration
|
||||
}
|
||||
|
||||
getSortOrder() {
|
||||
return 5
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ export const parseDurationValue = (
|
|||
|
||||
// If the value is a number, we assume it's already in seconds (i.e. from the backend).
|
||||
if (Number.isFinite(inputValue)) {
|
||||
return inputValue > 0 ? inputValue : null
|
||||
return inputValue
|
||||
}
|
||||
|
||||
let multiplier = 1
|
||||
|
|
|
@ -25,8 +25,6 @@ import ViewFilterTypeCollaborators from '@baserow/modules/database/components/vi
|
|||
import {
|
||||
FormulaFieldType,
|
||||
NumberFieldType,
|
||||
RatingFieldType,
|
||||
DurationFieldType,
|
||||
} from '@baserow/modules/database/fieldTypes'
|
||||
|
||||
export class ViewFilterType extends Registerable {
|
||||
|
@ -131,16 +129,36 @@ export class ViewFilterType extends Registerable {
|
|||
* list provided by getCompatibleFieldTypes to calculate this.
|
||||
*/
|
||||
fieldIsCompatible(field) {
|
||||
for (const typeOrFunc of this.getCompatibleFieldTypes()) {
|
||||
const valuesMap = this.getCompatibleFieldTypes().map((type) => [type, true])
|
||||
return this.getCompatibleFieldValue(field, valuesMap, false)
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a field and a map of field types to values, this method will return the
|
||||
* value that is compatible with the field. If no value is found the notFoundValue
|
||||
* will be returned.
|
||||
* This can be used to verify if a field is compatible with a filter type or to
|
||||
* return the correct component for the filter input.
|
||||
*
|
||||
* @param {object} field The field object that should be checked.
|
||||
* @param {object} valuesMap A list of tuple where the key is the field type or a function
|
||||
* that takes a field and returns a boolean and the value is the value that should be
|
||||
* returned if the field is compatible.
|
||||
* @param {any} notFoundValue The value that should be returned if no compatible value
|
||||
* is found.
|
||||
* @returns {any} The value that is compatible with the field or the notFoundValue.
|
||||
*/
|
||||
getCompatibleFieldValue(field, valuesMap, notFoundValue = null) {
|
||||
for (const [typeOrFunc, value] of valuesMap) {
|
||||
if (typeOrFunc instanceof Function) {
|
||||
if (typeOrFunc(field)) {
|
||||
return true
|
||||
return value
|
||||
}
|
||||
} else if (field.type === typeOrFunc) {
|
||||
return true
|
||||
return value
|
||||
}
|
||||
}
|
||||
return false
|
||||
return notFoundValue
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -166,7 +184,179 @@ export class ViewFilterType extends Registerable {
|
|||
}
|
||||
}
|
||||
|
||||
export class EqualViewFilterType extends ViewFilterType {
|
||||
/**
|
||||
* Base class for field-type specific filtering details.
|
||||
*
|
||||
* In some cases we want to have per field-type handling of certain aspects of
|
||||
* a filter: input component selection and value parsing logic.
|
||||
*
|
||||
* This is a base class defining common interface for such customizations
|
||||
*/
|
||||
class SpecificFieldViewFilterHandler {
|
||||
getInputComponent() {
|
||||
return null
|
||||
}
|
||||
|
||||
parseRowValue(value, field, fieldType) {
|
||||
return value
|
||||
}
|
||||
|
||||
parseFilterValue(value, field, fieldType) {
|
||||
return value
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle duration-specific filtering aspects:
|
||||
*
|
||||
* * input component should understand duration formats
|
||||
* * values should be parsed to duration value (a number of seconds).
|
||||
*
|
||||
*
|
||||
* Parsing is especially important because duration parsing result depends on duration
|
||||
* format picked. Filter value is passed as a string, and in case of duration, backend
|
||||
* will send a number of seconds. This, however, may be parsed as a number of minutes
|
||||
* or hours if a duration format picked uses minutes or hours as a lowest unit (i.e.
|
||||
* `d h m` or `d h` format).
|
||||
*
|
||||
* In case of parsing, this class ensures that a number string is passed as a Number
|
||||
* type to be consistent with backend's behavior.
|
||||
*
|
||||
*/
|
||||
class DurationFieldViewFilterHandler extends SpecificFieldViewFilterHandler {
|
||||
getInputComponent() {
|
||||
return ViewFilterTypeDuration
|
||||
}
|
||||
|
||||
_parseDuration(value, field, fieldType) {
|
||||
if (String(value === null ? '' : value).trim() === '') {
|
||||
return null
|
||||
}
|
||||
|
||||
const parsedValue = Number(value)
|
||||
if (_.isFinite(parsedValue)) {
|
||||
value = parsedValue
|
||||
}
|
||||
return fieldType.parseInputValue(field, value)
|
||||
}
|
||||
|
||||
parseRowValue(value, field, fieldType) {
|
||||
// already processed, can be returned as-is.
|
||||
if (_.isInteger(value)) {
|
||||
return value
|
||||
}
|
||||
return fieldType.parseInputValue(field, value)
|
||||
}
|
||||
|
||||
parseFilterValue(value, field, fieldType) {
|
||||
return this._parseDuration(value, field, fieldType)
|
||||
}
|
||||
}
|
||||
|
||||
class TextLikeFieldViewFilterHandler extends SpecificFieldViewFilterHandler {
|
||||
getInputComponent() {
|
||||
return ViewFilterTypeText
|
||||
}
|
||||
|
||||
parseRowValue(value, field, fieldType) {
|
||||
return (value === null ? '' : value).toString().toLowerCase().trim()
|
||||
}
|
||||
|
||||
parseFilterValue(value, field, fieldType) {
|
||||
return (value === null ? '' : value).toString().toLowerCase().trim()
|
||||
}
|
||||
}
|
||||
|
||||
class RatingFieldViewFilterHandler extends SpecificFieldViewFilterHandler {
|
||||
getInputComponent() {
|
||||
return ViewFilterTypeRating
|
||||
}
|
||||
|
||||
parseRowValue(value, field, fieldType) {
|
||||
if (value === '' || value === null) {
|
||||
return NaN
|
||||
}
|
||||
return Number(value.toString().toLowerCase().trim())
|
||||
}
|
||||
|
||||
parseFilterValue(value, field, fieldType) {
|
||||
if (value === '' || value === null) {
|
||||
return NaN
|
||||
}
|
||||
return Number(value.toString().toLowerCase().trim())
|
||||
}
|
||||
}
|
||||
|
||||
class NumberFieldViewFilterHandler extends SpecificFieldViewFilterHandler {
|
||||
getInputComponent() {
|
||||
return ViewFilterTypeNumber
|
||||
}
|
||||
|
||||
_parseNumberValue(value) {
|
||||
if (value === '' || value === null) {
|
||||
return NaN
|
||||
}
|
||||
return Number(value.toString().toLowerCase().trim())
|
||||
}
|
||||
|
||||
parseRowValue(value, field, fieldType) {
|
||||
return this._parseNumberValue(value)
|
||||
}
|
||||
|
||||
parseFilterValue(value, field, fieldType) {
|
||||
return this._parseNumberValue(value)
|
||||
}
|
||||
}
|
||||
|
||||
class SpecificFieldFilterType extends ViewFilterType {
|
||||
getFieldsMapping() {
|
||||
const map = [
|
||||
['duration', new DurationFieldViewFilterHandler()],
|
||||
[
|
||||
FormulaFieldType.compatibleWithFormulaTypes('duration'),
|
||||
new DurationFieldViewFilterHandler(),
|
||||
],
|
||||
['rating', new RatingFieldViewFilterHandler()],
|
||||
['number', new NumberFieldViewFilterHandler()],
|
||||
[
|
||||
FormulaFieldType.compatibleWithFormulaTypes('number'),
|
||||
new NumberFieldViewFilterHandler(),
|
||||
],
|
||||
['autonumber', new NumberFieldViewFilterHandler()],
|
||||
]
|
||||
return map
|
||||
}
|
||||
|
||||
getSpecificFieldFilterType(field) {
|
||||
const map = this.getFieldsMapping()
|
||||
return this.getCompatibleFieldValue(
|
||||
field,
|
||||
map,
|
||||
new TextLikeFieldViewFilterHandler()
|
||||
)
|
||||
}
|
||||
|
||||
getMatchesParsedValues(rowValue, filterValue, field, fieldType) {
|
||||
const specificFieldType = this.getSpecificFieldFilterType(field)
|
||||
const parsedRowValue = specificFieldType.parseRowValue(
|
||||
rowValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
const parsedFilterValue = specificFieldType.parseFilterValue(
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
return { rowVal: parsedRowValue, filterVal: parsedFilterValue }
|
||||
}
|
||||
|
||||
getInputComponent(field) {
|
||||
return this.getSpecificFieldFilterType(field).getInputComponent()
|
||||
}
|
||||
}
|
||||
|
||||
export class EqualViewFilterType extends SpecificFieldFilterType {
|
||||
static getType() {
|
||||
return 'equal'
|
||||
}
|
||||
|
@ -176,15 +366,6 @@ export class EqualViewFilterType extends ViewFilterType {
|
|||
return i18n.t('viewFilter.is')
|
||||
}
|
||||
|
||||
getInputComponent(field) {
|
||||
const inputComponent = {
|
||||
[RatingFieldType.getType()]: ViewFilterTypeRating,
|
||||
[NumberFieldType.getType()]: ViewFilterTypeNumber,
|
||||
[DurationFieldType.getType()]: ViewFilterTypeDuration,
|
||||
}
|
||||
return inputComponent[field?.type] || ViewFilterTypeText
|
||||
}
|
||||
|
||||
getCompatibleFieldTypes() {
|
||||
return [
|
||||
'text',
|
||||
|
@ -201,6 +382,7 @@ export class EqualViewFilterType extends ViewFilterType {
|
|||
'text',
|
||||
'char',
|
||||
'number',
|
||||
'duration',
|
||||
'url'
|
||||
),
|
||||
]
|
||||
|
@ -210,14 +392,18 @@ export class EqualViewFilterType extends ViewFilterType {
|
|||
if (rowValue === null) {
|
||||
rowValue = ''
|
||||
}
|
||||
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||
rowValue,
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
|
||||
rowValue = rowValue.toString().toLowerCase().trim()
|
||||
filterValue = filterValue.toString().toLowerCase().trim()
|
||||
return filterValue === '' || rowValue === filterValue
|
||||
return filterVal === '' || rowVal === filterVal
|
||||
}
|
||||
}
|
||||
|
||||
export class NotEqualViewFilterType extends ViewFilterType {
|
||||
export class NotEqualViewFilterType extends SpecificFieldFilterType {
|
||||
static getType() {
|
||||
return 'not_equal'
|
||||
}
|
||||
|
@ -227,15 +413,6 @@ export class NotEqualViewFilterType extends ViewFilterType {
|
|||
return i18n.t('viewFilter.isNot')
|
||||
}
|
||||
|
||||
getInputComponent(field) {
|
||||
const inputComponent = {
|
||||
[RatingFieldType.getType()]: ViewFilterTypeRating,
|
||||
[NumberFieldType.getType()]: ViewFilterTypeNumber,
|
||||
[DurationFieldType.getType()]: ViewFilterTypeDuration,
|
||||
}
|
||||
return inputComponent[field?.type] || ViewFilterTypeText
|
||||
}
|
||||
|
||||
getCompatibleFieldTypes() {
|
||||
return [
|
||||
'text',
|
||||
|
@ -252,6 +429,7 @@ export class NotEqualViewFilterType extends ViewFilterType {
|
|||
'text',
|
||||
'char',
|
||||
'number',
|
||||
'duration',
|
||||
'url'
|
||||
),
|
||||
]
|
||||
|
@ -262,9 +440,13 @@ export class NotEqualViewFilterType extends ViewFilterType {
|
|||
rowValue = ''
|
||||
}
|
||||
|
||||
rowValue = rowValue.toString().toLowerCase().trim()
|
||||
filterValue = filterValue.toString().toLowerCase().trim()
|
||||
return filterValue === '' || rowValue !== filterValue
|
||||
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||
rowValue,
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
return filterVal === '' || rowVal !== filterVal
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2032,26 +2214,18 @@ export class DateEqualsDayOfMonthViewFilterType extends LocalizedDateViewFilterT
|
|||
// Base filter type for basic numeric comparisons. It defines common logic for
|
||||
// 'lower than', 'lower than or equal', 'higher than' and 'higher than or equal'
|
||||
// view filter types.
|
||||
export class NumericComparisonViewFilterType extends ViewFilterType {
|
||||
export class NumericComparisonViewFilterType extends SpecificFieldFilterType {
|
||||
getExample() {
|
||||
return '100'
|
||||
}
|
||||
|
||||
getInputComponent(field) {
|
||||
const inputComponent = {
|
||||
[RatingFieldType.getType()]: ViewFilterTypeRating,
|
||||
[DurationFieldType.getType()]: ViewFilterTypeDuration,
|
||||
}
|
||||
return inputComponent[field?.type] || ViewFilterTypeNumber
|
||||
}
|
||||
|
||||
getCompatibleFieldTypes() {
|
||||
return [
|
||||
'number',
|
||||
'rating',
|
||||
'autonumber',
|
||||
'duration',
|
||||
FormulaFieldType.compatibleWithFormulaTypes('number'),
|
||||
FormulaFieldType.compatibleWithFormulaTypes('number', 'duration'),
|
||||
]
|
||||
}
|
||||
|
||||
|
@ -2076,9 +2250,17 @@ export class HigherThanViewFilterType extends NumericComparisonViewFilterType {
|
|||
return true
|
||||
}
|
||||
|
||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
||||
return Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal > fltVal
|
||||
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||
rowValue,
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
return (
|
||||
Number.isFinite(rowVal) &&
|
||||
Number.isFinite(filterVal) &&
|
||||
rowVal > filterVal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2097,10 +2279,16 @@ export class HigherThanOrEqualViewFilterType extends NumericComparisonViewFilter
|
|||
return true
|
||||
}
|
||||
|
||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
||||
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||
rowValue,
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
return (
|
||||
Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal >= fltVal
|
||||
Number.isFinite(rowVal) &&
|
||||
Number.isFinite(filterVal) &&
|
||||
rowVal >= filterVal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2119,10 +2307,18 @@ export class LowerThanViewFilterType extends NumericComparisonViewFilterType {
|
|||
if (filterValue === '') {
|
||||
return true
|
||||
}
|
||||
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||
rowValue,
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
|
||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
||||
return Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal < fltVal
|
||||
return (
|
||||
Number.isFinite(rowVal) &&
|
||||
Number.isFinite(filterVal) &&
|
||||
rowVal < filterVal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2141,10 +2337,17 @@ export class LowerThanOrEqualViewFilterType extends NumericComparisonViewFilterT
|
|||
return true
|
||||
}
|
||||
|
||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
||||
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||
rowValue,
|
||||
filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
|
||||
return (
|
||||
Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal <= fltVal
|
||||
Number.isFinite(rowVal) &&
|
||||
Number.isFinite(filterVal) &&
|
||||
rowVal <= filterVal
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@ -2737,6 +2940,7 @@ export class EmptyViewFilterType extends ViewFilterType {
|
|||
'boolean',
|
||||
'date',
|
||||
'number',
|
||||
'duration',
|
||||
'url',
|
||||
'single_select',
|
||||
FormulaFieldType.arrayOf('single_file'),
|
||||
|
@ -2802,6 +3006,7 @@ export class NotEmptyViewFilterType extends ViewFilterType {
|
|||
'boolean',
|
||||
'date',
|
||||
'number',
|
||||
'duration',
|
||||
'url',
|
||||
'single_select',
|
||||
FormulaFieldType.arrayOf('single_file'),
|
||||
|
|
|
@ -26,6 +26,8 @@ import {
|
|||
DateWithinDaysViewFilterType,
|
||||
DateWithinMonthsViewFilterType,
|
||||
DateWithinWeeksViewFilterType,
|
||||
EmptyViewFilterType,
|
||||
EqualViewFilterType,
|
||||
FilesLowerThanViewFilterType,
|
||||
HasFileTypeViewFilterType,
|
||||
HigherThanOrEqualViewFilterType,
|
||||
|
@ -38,6 +40,7 @@ import {
|
|||
LowerThanViewFilterType,
|
||||
MultipleSelectHasFilterType,
|
||||
MultipleSelectHasNotFilterType,
|
||||
NotEmptyViewFilterType,
|
||||
SingleSelectIsAnyOfViewFilterType,
|
||||
SingleSelectIsNoneOfViewFilterType,
|
||||
} from '@baserow/modules/database/viewFilters'
|
||||
|
@ -1168,81 +1171,479 @@ const numberIsEvenAndWholeCases = [
|
|||
},
|
||||
]
|
||||
|
||||
const durationHigherThanCases = [
|
||||
const durationHigherLowerThanCases = [
|
||||
{
|
||||
rowValue: null,
|
||||
filterValue: '1:01',
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
expected: false,
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expectedGte: false,
|
||||
expectedGt: false,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 60,
|
||||
filterValue: '0:01',
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
filterValue: '0:01', // will parse to one minute
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 59,
|
||||
filterValue: '0:01', // will parse to one minute
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expectedGte: false,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: true,
|
||||
},
|
||||
{
|
||||
rowValue: 59,
|
||||
filterValue: '1:00', // will parse to one minute
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: false,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: true,
|
||||
},
|
||||
|
||||
{
|
||||
rowValue: 60,
|
||||
filterValue: '0:01', // will parse to one second
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 120, // 2m
|
||||
filterValue: '0:01', // one minute
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 61,
|
||||
filterValue: '60', // one minute
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 61,
|
||||
filterValue: '60', // one minute
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86401,
|
||||
filterValue: '24:00:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86401, // 1d 1s
|
||||
filterValue: '86401', // 1d 1s
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: false,
|
||||
},
|
||||
|
||||
{
|
||||
rowValue: 86399,
|
||||
filterValue: '24:00:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: false,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: true,
|
||||
},
|
||||
{
|
||||
rowValue: 86399, // exact
|
||||
filterValue: '86399', // exact
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86399,
|
||||
filterValue: '24:00:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expectedGte: false,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: true,
|
||||
},
|
||||
|
||||
{
|
||||
rowValue: 86401,
|
||||
filterValue: '24:00:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86401,
|
||||
filterValue: '86401', // 24h
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: true,
|
||||
expectedLte: false,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86400,
|
||||
filterValue: '24:00:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86400,
|
||||
filterValue: '86399', // 24h
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expectedGte: true,
|
||||
expectedGt: false,
|
||||
expectedLte: true,
|
||||
expectedLt: false,
|
||||
},
|
||||
]
|
||||
|
||||
const durationEmptyNotEmptyCases = [
|
||||
{
|
||||
rowValue: null,
|
||||
filterValue: '',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
emptyExpected: true,
|
||||
notEmptyExpected: false,
|
||||
},
|
||||
{
|
||||
rowValue: '',
|
||||
filterValue: '',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
emptyExpected: true,
|
||||
notEmptyExpected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 1234,
|
||||
filterValue: '',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
emptyExpected: false,
|
||||
notEmptyExpected: true,
|
||||
},
|
||||
]
|
||||
|
||||
const durationEqualToValueCases = [
|
||||
{
|
||||
rowValue: null,
|
||||
filterValue: '1:01',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 120,
|
||||
filterValue: '0:01',
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
expected: true,
|
||||
rowValue: 20 * 60, // 20 min
|
||||
filterValue: '0:01', // 1 min
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
{
|
||||
rowValue: 61, // will be rounded to 0:01
|
||||
filterValue: 60,
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 61,
|
||||
filterValue: 60,
|
||||
context: { field: { duration_format: 'h:mm:ss' } },
|
||||
filterValue: '0:01', // 1 min
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 20 * 60,
|
||||
filterValue: '0:20',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: 864001,
|
||||
filterValue: '24:00:00',
|
||||
context: { field: { duration_format: 'h:mm:ss' } },
|
||||
expected: true,
|
||||
rowValue: 20 * 60 - 1,
|
||||
filterValue: '0:20',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 20 * 60 - 1,
|
||||
filterValue: '0:20:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
]
|
||||
|
||||
const durationLowerThanCases = [
|
||||
{
|
||||
rowValue: null,
|
||||
filterValue: '1:01',
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
rowValue: 61,
|
||||
filterValue: 60,
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 20,
|
||||
filterValue: '0:01',
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
rowValue: 1234,
|
||||
filterValue: 1234,
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: 120,
|
||||
filterValue: '0:01',
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
rowValue: 86399, // 24h -1s
|
||||
filterValue: '24:00:00',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 61, // will be rounded to 0:01
|
||||
filterValue: 60,
|
||||
context: { field: { duration_format: 'h:mm' } },
|
||||
rowValue: 86399,
|
||||
filterValue: '86400',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 59,
|
||||
filterValue: 60,
|
||||
context: { field: { duration_format: 'h:mm:ss' } },
|
||||
rowValue: 86399,
|
||||
filterValue: '86400',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: 86400,
|
||||
filterValue: '86402',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'd h',
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
|
||||
{
|
||||
rowValue: 86399,
|
||||
filterValue: '86399',
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm:ss',
|
||||
},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: 86399,
|
||||
filterValue: '24:00:00',
|
||||
context: { field: { duration_format: 'h:mm:ss' } },
|
||||
expected: true,
|
||||
context: {
|
||||
field: {
|
||||
type: 'formula',
|
||||
formula_type: 'duration',
|
||||
duration_format: 'h:mm',
|
||||
},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
]
|
||||
|
||||
|
@ -1892,8 +2293,40 @@ describe('All Tests', () => {
|
|||
).toBe(true)
|
||||
})
|
||||
|
||||
test.each(durationHigherThanCases)(
|
||||
'DurationHigherThanFilterType',
|
||||
test.each(durationEmptyNotEmptyCases)(
|
||||
'durationEmptyNotEmptyCases empty test on duration formula field: %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
const result = new EmptyViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.emptyExpected)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationEmptyNotEmptyCases)(
|
||||
'durationEmptyNotEmptyCases not empty test on duration formula field: %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
const result = new NotEmptyViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.notEmptyExpected)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationHigherThanFilterType duration field %j',
|
||||
(values) => {
|
||||
const fieldType = new DurationFieldType({
|
||||
app: testApp,
|
||||
|
@ -1905,11 +2338,61 @@ describe('All Tests', () => {
|
|||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expected)
|
||||
expect(result).toBe(values.expectedGt)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationLowerThanCases)('DurationLowerThanFilterType', (values) => {
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationHigherThanFilterType on duration formula field %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
field.formula_type = 'duration'
|
||||
const result = new HigherThanViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expectedGt)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationHigherOrEqualThanFilterType %j',
|
||||
(values) => {
|
||||
const fieldType = new DurationFieldType({
|
||||
app: testApp,
|
||||
})
|
||||
const { field } = values.context
|
||||
const result = new HigherThanOrEqualViewFilterType({
|
||||
app: testApp,
|
||||
}).matches(values.rowValue, values.filterValue, field, fieldType)
|
||||
expect(result).toBe(values.expectedGte)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationHigherThanOrEqualFilterType on duration formula field %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
field.formula_type = 'duration'
|
||||
const result = new HigherThanOrEqualViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expectedGte)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationLowerThanFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new DurationFieldType({ app })
|
||||
const { field } = values.context
|
||||
|
@ -1919,11 +2402,79 @@ describe('All Tests', () => {
|
|||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expectedLt)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationLowerThanFilterType on duration formula field %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
field.formula_type = 'duration'
|
||||
const result = new LowerThanViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expectedLt)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationLowerThanOrEqualFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new DurationFieldType({ app })
|
||||
const { field } = values.context
|
||||
const result = new LowerThanOrEqualViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expectedLte)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationHigherLowerThanCases)(
|
||||
'DurationLowerThanOrEqualFilterType on duration formula field %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
field.formula_type = 'duration'
|
||||
const result = new LowerThanOrEqualViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expectedLte)
|
||||
}
|
||||
)
|
||||
|
||||
test.each(durationEqualToValueCases)(
|
||||
'durationEqualToValueCases on duration formula field: %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const fieldType = new FormulaFieldType({ app })
|
||||
const { field } = values.context
|
||||
field.formula_type = 'duration'
|
||||
const result = new EqualViewFilterType({ app }).matches(
|
||||
values.rowValue,
|
||||
values.filterValue,
|
||||
field,
|
||||
fieldType
|
||||
)
|
||||
expect(result).toBe(values.expected)
|
||||
})
|
||||
}
|
||||
)
|
||||
|
||||
test.each(numberValueIsHigherThanCases)(
|
||||
'NumberHigherThanFilterType',
|
||||
'NumberHigherThanFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const result = new HigherThanViewFilterType({ app }).matches(
|
||||
|
@ -1937,7 +2488,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(numberValueIsHigherThanOrEqualCases)(
|
||||
'NumberHigherThanOrEqualFilterType',
|
||||
'NumberHigherThanOrEqualFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const result = new HigherThanOrEqualViewFilterType({ app }).matches(
|
||||
|
@ -1951,7 +2502,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(numberValueIsHigherThanCases)(
|
||||
'FormulaNumberHigherThanFilterType',
|
||||
'FormulaNumberHigherThanFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const result = new HigherThanViewFilterType({ app }).matches(
|
||||
|
@ -1965,7 +2516,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(numberValueIsLowerThanCases)(
|
||||
'NumberLowerThanFilterType',
|
||||
'NumberLowerThanFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const result = new LowerThanViewFilterType({ app }).matches(
|
||||
|
@ -1979,7 +2530,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(numberValueIsLowerThanOrEqualCases)(
|
||||
'NumberLowerThanOrEqualFilterType',
|
||||
'NumberLowerThanOrEqualFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const result = new LowerThanOrEqualViewFilterType({ app }).matches(
|
||||
|
@ -1993,7 +2544,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(numberValueIsLowerThanCases)(
|
||||
'FormulaNumberLowerThanFilterType',
|
||||
'FormulaNumberLowerThanFilterType %j',
|
||||
(values) => {
|
||||
const app = testApp.getApp()
|
||||
const result = new LowerThanViewFilterType({ app }).matches(
|
||||
|
@ -2007,7 +2558,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(singleSelectValuesInFilterCases)(
|
||||
'SingleSelectIsAnyOfViewFilterType',
|
||||
'SingleSelectIsAnyOfViewFilterType %j',
|
||||
(values) => {
|
||||
const fieldType = new SingleSelectFieldType()
|
||||
const field = {}
|
||||
|
@ -2019,7 +2570,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(singleSelectValuesInFilterCases)(
|
||||
'SingleSelectIsAnyOfViewFilterType',
|
||||
'SingleSelectIsAnyOfViewFilterType %j',
|
||||
(values) => {
|
||||
const fieldType = new FormulaFieldType()
|
||||
const field = {
|
||||
|
@ -2033,7 +2584,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(singleSelectValuesInFilterCases)(
|
||||
'SingleSelectIsNoneOfViewFilterType',
|
||||
'SingleSelectIsNoneOfViewFilterType %j',
|
||||
(values) => {
|
||||
const fieldType = new SingleSelectFieldType()
|
||||
const field = {}
|
||||
|
@ -2045,7 +2596,7 @@ describe('All Tests', () => {
|
|||
)
|
||||
|
||||
test.each(singleSelectValuesInFilterCases)(
|
||||
'SingleSelectIsNoneOfViewFilterType',
|
||||
'SingleSelectIsNoneOfViewFilterType %j',
|
||||
(values) => {
|
||||
const fieldType = new FormulaFieldType()
|
||||
const field = {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue