mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-27 06:00:37 +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,
|
BaserowFormulaTextType.type,
|
||||||
BaserowFormulaCharType.type,
|
BaserowFormulaCharType.type,
|
||||||
BaserowFormulaNumberType.type,
|
BaserowFormulaNumberType.type,
|
||||||
|
BaserowFormulaDurationType.type,
|
||||||
BaserowFormulaURLType.type,
|
BaserowFormulaURLType.type,
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -369,7 +370,7 @@ class NumericComparisonViewFilterType(ViewFilterType):
|
||||||
AutonumberFieldType.type,
|
AutonumberFieldType.type,
|
||||||
DurationFieldType.type,
|
DurationFieldType.type,
|
||||||
FormulaFieldType.compatible_with_formula_types(
|
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 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 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):
|
async def received_message(communicator: WebsocketCommunicator, message_type: str):
|
||||||
"""
|
"""
|
||||||
|
@ -32,3 +49,191 @@ async def get_message(communicator: WebsocketCommunicator, message_type: str):
|
||||||
return message
|
return message
|
||||||
except asyncio.exceptions.TimeoutError: # No more messages
|
except asyncio.exceptions.TimeoutError: # No more messages
|
||||||
return None
|
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
|
import typing
|
||||||
from dataclasses import dataclass
|
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
|
|
||||||
from django.contrib.auth.models import AbstractUser
|
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from baserow.contrib.database.fields.models import Field, LinkRowField, LookupField
|
from tests.baserow.contrib.database.utils import (
|
||||||
from baserow.contrib.database.rows.handler import RowHandler
|
boolean_field_factory,
|
||||||
from baserow.contrib.database.table.models import GeneratedTableModel, Table
|
email_field_factory,
|
||||||
from baserow.contrib.database.views.handler import ViewHandler
|
long_text_field_factory,
|
||||||
from baserow.contrib.database.views.models import GridView
|
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:
|
if typing.TYPE_CHECKING:
|
||||||
from baserow.test_utils.fixtures import Fixtures
|
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):
|
class BooleanLookupRow(int, Enum):
|
||||||
"""
|
"""
|
||||||
Helper enum for boolean lookup field filters tests.
|
Helper enum for boolean lookup field filters tests.
|
||||||
|
@ -46,85 +36,6 @@ class BooleanLookupRow(int, Enum):
|
||||||
NO_VALUES = 3
|
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(
|
def boolean_lookup_filter_proc(
|
||||||
data_fixture: "Fixtures",
|
data_fixture: "Fixtures",
|
||||||
filter_type_name: str,
|
filter_type_name: str,
|
||||||
|
@ -137,7 +48,7 @@ def boolean_lookup_filter_proc(
|
||||||
rows.
|
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)]
|
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])
|
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(
|
@pytest.mark.parametrize(
|
||||||
"target_field_factory,target_field_value_factory",
|
"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(
|
def test_has_empty_value_filter_text_field_types(
|
||||||
data_fixture, target_field_factory, target_field_value_factory
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_empty_value_filter_uuid_field_types(data_fixture):
|
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_1 = test_setup.other_table_model.objects.create()
|
||||||
other_row_2 = 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(
|
def test_has_not_empty_value_filter_text_field_types(
|
||||||
data_fixture, target_field_factory, target_field_value_factory
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_not_empty_value_filter_uuid_field_types(data_fixture):
|
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_1 = test_setup.other_table_model.objects.create()
|
||||||
other_row_2 = 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
|
@pytest.mark.django_db
|
||||||
def test_has_value_equal_filter_text_field_types(data_fixture, target_field_factory):
|
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(
|
other_row_A = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "A"}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_value_equal_filter_uuid_field_types(data_fixture):
|
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(
|
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(
|
def test_has_not_value_equal_filter_text_field_types(
|
||||||
data_fixture, target_field_factory
|
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(
|
other_row_A = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "A"}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_not_value_equal_filter_uuid_field_types(data_fixture):
|
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(
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_value_contains_filter_text_field_types(data_fixture, target_field_factory):
|
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(
|
other_row_John_Smith = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "John Smith"}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_value_contains_filter_uuid_field_types(data_fixture):
|
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(
|
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(
|
def test_has_not_value_contains_filter_text_field_types(
|
||||||
data_fixture, target_field_factory
|
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(
|
other_row_John_Smith = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "John Smith"}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_not_value_contains_filter_uuid_field_types(data_fixture):
|
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(
|
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(
|
def test_has_value_contains_word_filter_text_field_types(
|
||||||
data_fixture, target_field_factory
|
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(
|
other_row_1 = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "This is a sentence."}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_value_contains_word_filter_uuid_field_types(data_fixture):
|
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(
|
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(
|
def test_has_not_value_contains_word_filter_text_field_types(
|
||||||
data_fixture, target_field_factory
|
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(
|
other_row_1 = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "This is a sentence."}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_not_value_contains_word_filter_uuid_field_types(data_fixture):
|
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(
|
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(
|
def test_has_value_length_is_lower_than_text_field_types(
|
||||||
data_fixture, target_field_factory
|
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(
|
other_row_10a = test_setup.other_table_model.objects.create(
|
||||||
**{f"field_{test_setup.target_field.id}": "aaaaaaaaaa"}
|
**{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
|
@pytest.mark.django_db
|
||||||
def test_has_value_length_is_lower_than_uuid_field_types(data_fixture):
|
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_1 = test_setup.other_table_model.objects.create()
|
||||||
other_row_2 = 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(
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_value_equal_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_not_value_equal_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_value_contains_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_not_value_contains_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_value_contains_word_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_not_value_contains_word_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_any_select_option_equal_filter_single_select_field(data_fixture):
|
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_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")
|
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
|
@pytest.mark.django_db
|
||||||
def test_has_none_select_option_equal_filter_single_select_field(data_fixture):
|
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_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")
|
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 {
|
export default {
|
||||||
name: 'ViewFilterTypeDuration',
|
name: 'ViewFilterTypeDuration',
|
||||||
mixins: [filterTypeInput, durationField],
|
mixins: [filterTypeInput, durationField],
|
||||||
|
watch: {
|
||||||
|
'field.duration_format': {
|
||||||
|
handler() {
|
||||||
|
this.updateFormattedValue(this.field, this.filter.value)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.updateCopy(this.field, this.filter.value)
|
this.updateCopy(this.field, this.filter.value)
|
||||||
this.updateFormattedValue(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 { UploadFileUserFileUploadType } from '@baserow/modules/core/userFileUploadTypes'
|
||||||
import _ from 'lodash'
|
import _ from 'lodash'
|
||||||
import { trueValues } from '@baserow/modules/core/utils/constants'
|
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 {
|
export class FieldType extends Registerable {
|
||||||
/**
|
/**
|
||||||
|
@ -1308,6 +1310,10 @@ export class NumberFieldType extends FieldType {
|
||||||
return RowHistoryFieldNumber
|
return RowHistoryFieldNumber
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilterInputComponent(field, filterType) {
|
||||||
|
return ViewFilterTypeNumber
|
||||||
|
}
|
||||||
|
|
||||||
getSortIndicator() {
|
getSortIndicator() {
|
||||||
return ['text', '1', '9']
|
return ['text', '1', '9']
|
||||||
}
|
}
|
||||||
|
@ -2475,6 +2481,10 @@ export class DurationFieldType extends FieldType {
|
||||||
return FunctionalGridViewFieldDuration
|
return FunctionalGridViewFieldDuration
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilterInputComponent(field, filterType) {
|
||||||
|
return ViewFilterTypeDuration
|
||||||
|
}
|
||||||
|
|
||||||
getCanImport() {
|
getCanImport() {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,6 +65,7 @@ import {
|
||||||
genericHasValueContainsFilter,
|
genericHasValueContainsFilter,
|
||||||
} from '@baserow/modules/database/utils/fieldFilters'
|
} from '@baserow/modules/database/utils/fieldFilters'
|
||||||
import ViewFilterTypeSelectOptions from '@baserow/modules/database/components/view/ViewFilterTypeSelectOptions.vue'
|
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 {
|
export class BaserowFormulaTypeDefinition extends Registerable {
|
||||||
getIconClass() {
|
getIconClass() {
|
||||||
|
@ -457,6 +458,10 @@ export class BaserowFormulaDurationType extends BaserowFormulaTypeDefinition {
|
||||||
return RowEditFieldDurationReadOnly
|
return RowEditFieldDurationReadOnly
|
||||||
}
|
}
|
||||||
|
|
||||||
|
getFilterInputComponent(field, filterType) {
|
||||||
|
return ViewFilterTypeDuration
|
||||||
|
}
|
||||||
|
|
||||||
getSortOrder() {
|
getSortOrder() {
|
||||||
return 5
|
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 the value is a number, we assume it's already in seconds (i.e. from the backend).
|
||||||
if (Number.isFinite(inputValue)) {
|
if (Number.isFinite(inputValue)) {
|
||||||
return inputValue > 0 ? inputValue : null
|
return inputValue
|
||||||
}
|
}
|
||||||
|
|
||||||
let multiplier = 1
|
let multiplier = 1
|
||||||
|
|
|
@ -25,8 +25,6 @@ import ViewFilterTypeCollaborators from '@baserow/modules/database/components/vi
|
||||||
import {
|
import {
|
||||||
FormulaFieldType,
|
FormulaFieldType,
|
||||||
NumberFieldType,
|
NumberFieldType,
|
||||||
RatingFieldType,
|
|
||||||
DurationFieldType,
|
|
||||||
} from '@baserow/modules/database/fieldTypes'
|
} from '@baserow/modules/database/fieldTypes'
|
||||||
|
|
||||||
export class ViewFilterType extends Registerable {
|
export class ViewFilterType extends Registerable {
|
||||||
|
@ -131,16 +129,36 @@ export class ViewFilterType extends Registerable {
|
||||||
* list provided by getCompatibleFieldTypes to calculate this.
|
* list provided by getCompatibleFieldTypes to calculate this.
|
||||||
*/
|
*/
|
||||||
fieldIsCompatible(field) {
|
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 instanceof Function) {
|
||||||
if (typeOrFunc(field)) {
|
if (typeOrFunc(field)) {
|
||||||
return true
|
return value
|
||||||
}
|
}
|
||||||
} else if (field.type === typeOrFunc) {
|
} 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() {
|
static getType() {
|
||||||
return 'equal'
|
return 'equal'
|
||||||
}
|
}
|
||||||
|
@ -176,15 +366,6 @@ export class EqualViewFilterType extends ViewFilterType {
|
||||||
return i18n.t('viewFilter.is')
|
return i18n.t('viewFilter.is')
|
||||||
}
|
}
|
||||||
|
|
||||||
getInputComponent(field) {
|
|
||||||
const inputComponent = {
|
|
||||||
[RatingFieldType.getType()]: ViewFilterTypeRating,
|
|
||||||
[NumberFieldType.getType()]: ViewFilterTypeNumber,
|
|
||||||
[DurationFieldType.getType()]: ViewFilterTypeDuration,
|
|
||||||
}
|
|
||||||
return inputComponent[field?.type] || ViewFilterTypeText
|
|
||||||
}
|
|
||||||
|
|
||||||
getCompatibleFieldTypes() {
|
getCompatibleFieldTypes() {
|
||||||
return [
|
return [
|
||||||
'text',
|
'text',
|
||||||
|
@ -201,6 +382,7 @@ export class EqualViewFilterType extends ViewFilterType {
|
||||||
'text',
|
'text',
|
||||||
'char',
|
'char',
|
||||||
'number',
|
'number',
|
||||||
|
'duration',
|
||||||
'url'
|
'url'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -210,14 +392,18 @@ export class EqualViewFilterType extends ViewFilterType {
|
||||||
if (rowValue === null) {
|
if (rowValue === null) {
|
||||||
rowValue = ''
|
rowValue = ''
|
||||||
}
|
}
|
||||||
|
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||||
|
rowValue,
|
||||||
|
filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
|
||||||
rowValue = rowValue.toString().toLowerCase().trim()
|
return filterVal === '' || rowVal === filterVal
|
||||||
filterValue = filterValue.toString().toLowerCase().trim()
|
|
||||||
return filterValue === '' || rowValue === filterValue
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class NotEqualViewFilterType extends ViewFilterType {
|
export class NotEqualViewFilterType extends SpecificFieldFilterType {
|
||||||
static getType() {
|
static getType() {
|
||||||
return 'not_equal'
|
return 'not_equal'
|
||||||
}
|
}
|
||||||
|
@ -227,15 +413,6 @@ export class NotEqualViewFilterType extends ViewFilterType {
|
||||||
return i18n.t('viewFilter.isNot')
|
return i18n.t('viewFilter.isNot')
|
||||||
}
|
}
|
||||||
|
|
||||||
getInputComponent(field) {
|
|
||||||
const inputComponent = {
|
|
||||||
[RatingFieldType.getType()]: ViewFilterTypeRating,
|
|
||||||
[NumberFieldType.getType()]: ViewFilterTypeNumber,
|
|
||||||
[DurationFieldType.getType()]: ViewFilterTypeDuration,
|
|
||||||
}
|
|
||||||
return inputComponent[field?.type] || ViewFilterTypeText
|
|
||||||
}
|
|
||||||
|
|
||||||
getCompatibleFieldTypes() {
|
getCompatibleFieldTypes() {
|
||||||
return [
|
return [
|
||||||
'text',
|
'text',
|
||||||
|
@ -252,6 +429,7 @@ export class NotEqualViewFilterType extends ViewFilterType {
|
||||||
'text',
|
'text',
|
||||||
'char',
|
'char',
|
||||||
'number',
|
'number',
|
||||||
|
'duration',
|
||||||
'url'
|
'url'
|
||||||
),
|
),
|
||||||
]
|
]
|
||||||
|
@ -262,9 +440,13 @@ export class NotEqualViewFilterType extends ViewFilterType {
|
||||||
rowValue = ''
|
rowValue = ''
|
||||||
}
|
}
|
||||||
|
|
||||||
rowValue = rowValue.toString().toLowerCase().trim()
|
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||||
filterValue = filterValue.toString().toLowerCase().trim()
|
rowValue,
|
||||||
return filterValue === '' || rowValue !== filterValue
|
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
|
// 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'
|
// 'lower than', 'lower than or equal', 'higher than' and 'higher than or equal'
|
||||||
// view filter types.
|
// view filter types.
|
||||||
export class NumericComparisonViewFilterType extends ViewFilterType {
|
export class NumericComparisonViewFilterType extends SpecificFieldFilterType {
|
||||||
getExample() {
|
getExample() {
|
||||||
return '100'
|
return '100'
|
||||||
}
|
}
|
||||||
|
|
||||||
getInputComponent(field) {
|
|
||||||
const inputComponent = {
|
|
||||||
[RatingFieldType.getType()]: ViewFilterTypeRating,
|
|
||||||
[DurationFieldType.getType()]: ViewFilterTypeDuration,
|
|
||||||
}
|
|
||||||
return inputComponent[field?.type] || ViewFilterTypeNumber
|
|
||||||
}
|
|
||||||
|
|
||||||
getCompatibleFieldTypes() {
|
getCompatibleFieldTypes() {
|
||||||
return [
|
return [
|
||||||
'number',
|
'number',
|
||||||
'rating',
|
'rating',
|
||||||
'autonumber',
|
'autonumber',
|
||||||
'duration',
|
'duration',
|
||||||
FormulaFieldType.compatibleWithFormulaTypes('number'),
|
FormulaFieldType.compatibleWithFormulaTypes('number', 'duration'),
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2076,9 +2250,17 @@ export class HigherThanViewFilterType extends NumericComparisonViewFilterType {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
rowValue,
|
||||||
return Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal > fltVal
|
filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
return (
|
||||||
|
Number.isFinite(rowVal) &&
|
||||||
|
Number.isFinite(filterVal) &&
|
||||||
|
rowVal > filterVal
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2097,10 +2279,16 @@ export class HigherThanOrEqualViewFilterType extends NumericComparisonViewFilter
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
rowValue,
|
||||||
|
filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
return (
|
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 === '') {
|
if (filterValue === '') {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||||
|
rowValue,
|
||||||
|
filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
|
||||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
return (
|
||||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
Number.isFinite(rowVal) &&
|
||||||
return Number.isFinite(rowVal) && Number.isFinite(fltVal) && rowVal < fltVal
|
Number.isFinite(filterVal) &&
|
||||||
|
rowVal < filterVal
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -2141,10 +2337,17 @@ export class LowerThanOrEqualViewFilterType extends NumericComparisonViewFilterT
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
const rowVal = fieldType.parseInputValue(field, rowValue)
|
const { rowVal, filterVal } = this.getMatchesParsedValues(
|
||||||
const fltVal = fieldType.parseInputValue(field, filterValue)
|
rowValue,
|
||||||
|
filterValue,
|
||||||
|
field,
|
||||||
|
fieldType
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
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',
|
'boolean',
|
||||||
'date',
|
'date',
|
||||||
'number',
|
'number',
|
||||||
|
'duration',
|
||||||
'url',
|
'url',
|
||||||
'single_select',
|
'single_select',
|
||||||
FormulaFieldType.arrayOf('single_file'),
|
FormulaFieldType.arrayOf('single_file'),
|
||||||
|
@ -2802,6 +3006,7 @@ export class NotEmptyViewFilterType extends ViewFilterType {
|
||||||
'boolean',
|
'boolean',
|
||||||
'date',
|
'date',
|
||||||
'number',
|
'number',
|
||||||
|
'duration',
|
||||||
'url',
|
'url',
|
||||||
'single_select',
|
'single_select',
|
||||||
FormulaFieldType.arrayOf('single_file'),
|
FormulaFieldType.arrayOf('single_file'),
|
||||||
|
|
|
@ -26,6 +26,8 @@ import {
|
||||||
DateWithinDaysViewFilterType,
|
DateWithinDaysViewFilterType,
|
||||||
DateWithinMonthsViewFilterType,
|
DateWithinMonthsViewFilterType,
|
||||||
DateWithinWeeksViewFilterType,
|
DateWithinWeeksViewFilterType,
|
||||||
|
EmptyViewFilterType,
|
||||||
|
EqualViewFilterType,
|
||||||
FilesLowerThanViewFilterType,
|
FilesLowerThanViewFilterType,
|
||||||
HasFileTypeViewFilterType,
|
HasFileTypeViewFilterType,
|
||||||
HigherThanOrEqualViewFilterType,
|
HigherThanOrEqualViewFilterType,
|
||||||
|
@ -38,6 +40,7 @@ import {
|
||||||
LowerThanViewFilterType,
|
LowerThanViewFilterType,
|
||||||
MultipleSelectHasFilterType,
|
MultipleSelectHasFilterType,
|
||||||
MultipleSelectHasNotFilterType,
|
MultipleSelectHasNotFilterType,
|
||||||
|
NotEmptyViewFilterType,
|
||||||
SingleSelectIsAnyOfViewFilterType,
|
SingleSelectIsAnyOfViewFilterType,
|
||||||
SingleSelectIsNoneOfViewFilterType,
|
SingleSelectIsNoneOfViewFilterType,
|
||||||
} from '@baserow/modules/database/viewFilters'
|
} from '@baserow/modules/database/viewFilters'
|
||||||
|
@ -1168,81 +1171,479 @@ const numberIsEvenAndWholeCases = [
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
const durationHigherThanCases = [
|
const durationHigherLowerThanCases = [
|
||||||
{
|
{
|
||||||
rowValue: null,
|
rowValue: null,
|
||||||
filterValue: '1:01',
|
filterValue: '1:01',
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
context: {
|
||||||
expected: false,
|
field: {
|
||||||
|
type: 'formula',
|
||||||
|
formula_type: 'duration',
|
||||||
|
duration_format: 'h:mm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expectedGte: false,
|
||||||
|
expectedGt: false,
|
||||||
|
expectedLte: false,
|
||||||
|
expectedLt: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 60,
|
rowValue: 60,
|
||||||
filterValue: '0:01',
|
filterValue: '0:01', // will parse to one minute
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
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,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 120,
|
rowValue: 20 * 60, // 20 min
|
||||||
filterValue: '0:01',
|
filterValue: '0:01', // 1 min
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
context: {
|
||||||
expected: true,
|
field: {
|
||||||
},
|
type: 'formula',
|
||||||
{
|
formula_type: 'duration',
|
||||||
rowValue: 61, // will be rounded to 0:01
|
duration_format: 'h:mm',
|
||||||
filterValue: 60,
|
},
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 61,
|
rowValue: 61,
|
||||||
filterValue: 60,
|
filterValue: '0:01', // 1 min
|
||||||
context: { field: { duration_format: 'h:mm:ss' } },
|
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,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 864001,
|
rowValue: 20 * 60 - 1,
|
||||||
filterValue: '24:00:00',
|
filterValue: '0:20',
|
||||||
context: { field: { duration_format: 'h:mm:ss' } },
|
context: {
|
||||||
expected: true,
|
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,
|
rowValue: 61,
|
||||||
filterValue: '1:01',
|
filterValue: 60,
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
context: {
|
||||||
|
field: {
|
||||||
|
type: 'formula',
|
||||||
|
formula_type: 'duration',
|
||||||
|
duration_format: 'h:mm',
|
||||||
|
},
|
||||||
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 20,
|
rowValue: 1234,
|
||||||
filterValue: '0:01',
|
filterValue: 1234,
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
context: {
|
||||||
|
field: {
|
||||||
|
type: 'formula',
|
||||||
|
formula_type: 'duration',
|
||||||
|
duration_format: 'h:mm:ss',
|
||||||
|
},
|
||||||
|
},
|
||||||
expected: true,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 120,
|
rowValue: 86399, // 24h -1s
|
||||||
filterValue: '0:01',
|
filterValue: '24:00:00',
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
context: {
|
||||||
|
field: {
|
||||||
|
type: 'formula',
|
||||||
|
formula_type: 'duration',
|
||||||
|
duration_format: 'h:mm:ss',
|
||||||
|
},
|
||||||
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 61, // will be rounded to 0:01
|
rowValue: 86399,
|
||||||
filterValue: 60,
|
filterValue: '86400',
|
||||||
context: { field: { duration_format: 'h:mm' } },
|
context: {
|
||||||
|
field: {
|
||||||
|
type: 'formula',
|
||||||
|
formula_type: 'duration',
|
||||||
|
duration_format: 'h:mm:ss',
|
||||||
|
},
|
||||||
|
},
|
||||||
expected: false,
|
expected: false,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 59,
|
rowValue: 86399,
|
||||||
filterValue: 60,
|
filterValue: '86400',
|
||||||
context: { field: { duration_format: 'h:mm:ss' } },
|
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,
|
expected: true,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
rowValue: 86399,
|
rowValue: 86399,
|
||||||
filterValue: '24:00:00',
|
filterValue: '24:00:00',
|
||||||
context: { field: { duration_format: 'h:mm:ss' } },
|
context: {
|
||||||
expected: true,
|
field: {
|
||||||
|
type: 'formula',
|
||||||
|
formula_type: 'duration',
|
||||||
|
duration_format: 'h:mm',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
expected: false,
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -1892,8 +2293,40 @@ describe('All Tests', () => {
|
||||||
).toBe(true)
|
).toBe(true)
|
||||||
})
|
})
|
||||||
|
|
||||||
test.each(durationHigherThanCases)(
|
test.each(durationEmptyNotEmptyCases)(
|
||||||
'DurationHigherThanFilterType',
|
'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) => {
|
(values) => {
|
||||||
const fieldType = new DurationFieldType({
|
const fieldType = new DurationFieldType({
|
||||||
app: testApp,
|
app: testApp,
|
||||||
|
@ -1905,25 +2338,143 @@ describe('All Tests', () => {
|
||||||
field,
|
field,
|
||||||
fieldType
|
fieldType
|
||||||
)
|
)
|
||||||
|
expect(result).toBe(values.expectedGt)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
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
|
||||||
|
const result = new LowerThanViewFilterType({ app }).matches(
|
||||||
|
values.rowValue,
|
||||||
|
values.filterValue,
|
||||||
|
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)
|
expect(result).toBe(values.expected)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(durationLowerThanCases)('DurationLowerThanFilterType', (values) => {
|
|
||||||
const app = testApp.getApp()
|
|
||||||
const fieldType = new DurationFieldType({ app })
|
|
||||||
const { field } = values.context
|
|
||||||
const result = new LowerThanViewFilterType({ app }).matches(
|
|
||||||
values.rowValue,
|
|
||||||
values.filterValue,
|
|
||||||
field,
|
|
||||||
fieldType
|
|
||||||
)
|
|
||||||
expect(result).toBe(values.expected)
|
|
||||||
})
|
|
||||||
|
|
||||||
test.each(numberValueIsHigherThanCases)(
|
test.each(numberValueIsHigherThanCases)(
|
||||||
'NumberHigherThanFilterType',
|
'NumberHigherThanFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const app = testApp.getApp()
|
const app = testApp.getApp()
|
||||||
const result = new HigherThanViewFilterType({ app }).matches(
|
const result = new HigherThanViewFilterType({ app }).matches(
|
||||||
|
@ -1937,7 +2488,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(numberValueIsHigherThanOrEqualCases)(
|
test.each(numberValueIsHigherThanOrEqualCases)(
|
||||||
'NumberHigherThanOrEqualFilterType',
|
'NumberHigherThanOrEqualFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const app = testApp.getApp()
|
const app = testApp.getApp()
|
||||||
const result = new HigherThanOrEqualViewFilterType({ app }).matches(
|
const result = new HigherThanOrEqualViewFilterType({ app }).matches(
|
||||||
|
@ -1951,7 +2502,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(numberValueIsHigherThanCases)(
|
test.each(numberValueIsHigherThanCases)(
|
||||||
'FormulaNumberHigherThanFilterType',
|
'FormulaNumberHigherThanFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const app = testApp.getApp()
|
const app = testApp.getApp()
|
||||||
const result = new HigherThanViewFilterType({ app }).matches(
|
const result = new HigherThanViewFilterType({ app }).matches(
|
||||||
|
@ -1965,7 +2516,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(numberValueIsLowerThanCases)(
|
test.each(numberValueIsLowerThanCases)(
|
||||||
'NumberLowerThanFilterType',
|
'NumberLowerThanFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const app = testApp.getApp()
|
const app = testApp.getApp()
|
||||||
const result = new LowerThanViewFilterType({ app }).matches(
|
const result = new LowerThanViewFilterType({ app }).matches(
|
||||||
|
@ -1979,7 +2530,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(numberValueIsLowerThanOrEqualCases)(
|
test.each(numberValueIsLowerThanOrEqualCases)(
|
||||||
'NumberLowerThanOrEqualFilterType',
|
'NumberLowerThanOrEqualFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const app = testApp.getApp()
|
const app = testApp.getApp()
|
||||||
const result = new LowerThanOrEqualViewFilterType({ app }).matches(
|
const result = new LowerThanOrEqualViewFilterType({ app }).matches(
|
||||||
|
@ -1993,7 +2544,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(numberValueIsLowerThanCases)(
|
test.each(numberValueIsLowerThanCases)(
|
||||||
'FormulaNumberLowerThanFilterType',
|
'FormulaNumberLowerThanFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const app = testApp.getApp()
|
const app = testApp.getApp()
|
||||||
const result = new LowerThanViewFilterType({ app }).matches(
|
const result = new LowerThanViewFilterType({ app }).matches(
|
||||||
|
@ -2007,7 +2558,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(singleSelectValuesInFilterCases)(
|
test.each(singleSelectValuesInFilterCases)(
|
||||||
'SingleSelectIsAnyOfViewFilterType',
|
'SingleSelectIsAnyOfViewFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const fieldType = new SingleSelectFieldType()
|
const fieldType = new SingleSelectFieldType()
|
||||||
const field = {}
|
const field = {}
|
||||||
|
@ -2019,7 +2570,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(singleSelectValuesInFilterCases)(
|
test.each(singleSelectValuesInFilterCases)(
|
||||||
'SingleSelectIsAnyOfViewFilterType',
|
'SingleSelectIsAnyOfViewFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const fieldType = new FormulaFieldType()
|
const fieldType = new FormulaFieldType()
|
||||||
const field = {
|
const field = {
|
||||||
|
@ -2033,7 +2584,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(singleSelectValuesInFilterCases)(
|
test.each(singleSelectValuesInFilterCases)(
|
||||||
'SingleSelectIsNoneOfViewFilterType',
|
'SingleSelectIsNoneOfViewFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const fieldType = new SingleSelectFieldType()
|
const fieldType = new SingleSelectFieldType()
|
||||||
const field = {}
|
const field = {}
|
||||||
|
@ -2045,7 +2596,7 @@ describe('All Tests', () => {
|
||||||
)
|
)
|
||||||
|
|
||||||
test.each(singleSelectValuesInFilterCases)(
|
test.each(singleSelectValuesInFilterCases)(
|
||||||
'SingleSelectIsNoneOfViewFilterType',
|
'SingleSelectIsNoneOfViewFilterType %j',
|
||||||
(values) => {
|
(values) => {
|
||||||
const fieldType = new FormulaFieldType()
|
const fieldType = new FormulaFieldType()
|
||||||
const field = {
|
const field = {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue