mirror of
https://gitlab.com/bramw/baserow.git
synced 2025-04-15 01:28:30 +00:00
Resolve "Add new date filter types 'is within X days', 'is within X weeks' and 'is within X months'"
This commit is contained in:
parent
85dcc341e0
commit
4b9be991cc
8 changed files with 602 additions and 38 deletions
backend
src/baserow/contrib/database
tests/baserow/contrib/database/view
changelog/entries/unreleased/feature
web-frontend
|
@ -285,6 +285,9 @@ class DatabaseConfig(AppConfig):
|
|||
DateEqualsTodayViewFilterType,
|
||||
DateEqualsYearsAgoViewFilterType,
|
||||
DateEqualViewFilterType,
|
||||
DateIsWithinXDaysViewFilterType,
|
||||
DateIsWithinXMonthsViewFilterType,
|
||||
DateIsWithinXWeeksViewFilterType,
|
||||
DateNotEqualViewFilterType,
|
||||
DoesntContainWordViewFilterType,
|
||||
EmptyViewFilterType,
|
||||
|
@ -326,6 +329,9 @@ class DatabaseConfig(AppConfig):
|
|||
view_filter_type_registry.register(DateEqualsTodayViewFilterType())
|
||||
view_filter_type_registry.register(DateBeforeTodayViewFilterType())
|
||||
view_filter_type_registry.register(DateAfterTodayViewFilterType())
|
||||
view_filter_type_registry.register(DateIsWithinXDaysViewFilterType())
|
||||
view_filter_type_registry.register(DateIsWithinXWeeksViewFilterType())
|
||||
view_filter_type_registry.register(DateIsWithinXMonthsViewFilterType())
|
||||
view_filter_type_registry.register(DateEqualsDaysAgoViewFilterType())
|
||||
view_filter_type_registry.register(DateEqualsMonthsAgoViewFilterType())
|
||||
view_filter_type_registry.register(DateEqualsYearsAgoViewFilterType())
|
||||
|
|
|
@ -356,14 +356,17 @@ class TimezoneAwareDateViewFilterType(ViewFilterType):
|
|||
return datetime_value.astimezone(timezone)
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[datetime, date]
|
||||
self, field_name: str, aware_filter_date: Union[datetime, date], now: datetime
|
||||
) -> Dict:
|
||||
"""
|
||||
Returns a dictionary that can be used to create a Q object.
|
||||
|
||||
:param field_name: The name of the field that should be used in the query.
|
||||
:param aware_filter_date: The date that should be used to compare with the
|
||||
field value.
|
||||
:param field_name: The name of the field that should be used in the
|
||||
query.
|
||||
:param aware_filter_date: The date that should be used to compare with
|
||||
the field value.
|
||||
:param now: The current aware datetime that can be used to compare with
|
||||
the field value.
|
||||
"""
|
||||
|
||||
raise NotImplementedError()
|
||||
|
@ -465,7 +468,9 @@ class TimezoneAwareDateViewFilterType(ViewFilterType):
|
|||
|
||||
query_dict = {
|
||||
f"{field_name}__isnull": False, # makes `NotViewFilterTypeMixin` work with timezones
|
||||
**self.get_filter_query_dict(query_field_name, filter_date),
|
||||
**self.get_filter_query_dict(
|
||||
query_field_name, filter_date, datetime.now(timezone)
|
||||
),
|
||||
}
|
||||
return AnnotatedQ(annotation=annotation, q=query_dict)
|
||||
|
||||
|
@ -480,7 +485,7 @@ class DateEqualViewFilterType(TimezoneAwareDateViewFilterType):
|
|||
type = "date_equal"
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {field_name: aware_filter_date}
|
||||
|
||||
|
@ -495,7 +500,7 @@ class DateBeforeViewFilterType(TimezoneAwareDateViewFilterType):
|
|||
type = "date_before"
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict[str, Any]:
|
||||
return {f"{field_name}__lt": aware_filter_date}
|
||||
|
||||
|
@ -510,7 +515,7 @@ class DateAfterViewFilterType(TimezoneAwareDateViewFilterType):
|
|||
type = "date_after"
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict[str, Any]:
|
||||
return {f"{field_name}__gt": aware_filter_date}
|
||||
|
||||
|
@ -570,7 +575,7 @@ class DateEqualsTodayViewFilterType(
|
|||
return datetime.now(tz=timezone).date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {field_name: aware_filter_date}
|
||||
|
||||
|
@ -590,7 +595,7 @@ class DateBeforeTodayViewFilterType(
|
|||
return (datetime.now(tz=timezone) - timedelta(days=1)).date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {f"{field_name}__lte": aware_filter_date}
|
||||
|
||||
|
@ -610,7 +615,7 @@ class DateAfterTodayViewFilterType(
|
|||
return (datetime.now(tz=timezone) + timedelta(days=1)).date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {f"{field_name}__gte": aware_filter_date}
|
||||
|
||||
|
@ -631,7 +636,7 @@ class DateEqualsCurrentWeekViewFilterType(
|
|||
return datetime.now(tz=timezone).date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
week_of_year = aware_filter_date.isocalendar().week
|
||||
return {
|
||||
|
@ -656,7 +661,7 @@ class DateEqualsCurrentMonthViewFilterType(
|
|||
return datetime.now(tz=timezone).date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {
|
||||
f"{field_name}__month": aware_filter_date.month,
|
||||
|
@ -680,11 +685,124 @@ class DateEqualsCurrentYearViewFilterType(
|
|||
return datetime.now(tz=timezone).date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {f"{field_name}__year": aware_filter_date.year}
|
||||
|
||||
|
||||
def get_is_within_filter_query_dict(
|
||||
field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
"""
|
||||
The utility function checks if the field value is between today's date and
|
||||
the date specified in the filter value and returns the query dict.
|
||||
It ensure that the filter works correctly even if the filter date is before
|
||||
today's date.
|
||||
"""
|
||||
|
||||
op1, op2 = ("lte", "gte") if aware_filter_date >= now.date() else ("gte", "lte")
|
||||
|
||||
return {
|
||||
f"{field_name}__{op1}": aware_filter_date,
|
||||
f"{field_name}__{op2}": now.date(),
|
||||
}
|
||||
|
||||
|
||||
class DateIsWithinXDaysViewFilterType(TimezoneAwareDateViewFilterType):
|
||||
"""
|
||||
Is within X days filter checks if the field value is between today's date
|
||||
and the number of days specified in the filter.
|
||||
|
||||
The value of the filter is expected to be a string like "Europe/Rome?1". It
|
||||
uses character ? as separator between the timezone and the number of days.
|
||||
"""
|
||||
|
||||
type = "date_within_days"
|
||||
|
||||
def get_filter_date(
|
||||
self, filter_value: str, timezone: pytz.BaseTzInfo
|
||||
) -> Union[datetime, date]:
|
||||
"""
|
||||
Zero days means today, one day means today and tomorrow, and so on.
|
||||
"""
|
||||
|
||||
number_of_days = int(filter_value)
|
||||
|
||||
filter_date = datetime.now(tz=timezone) + relativedelta(days=number_of_days)
|
||||
return filter_date.date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return get_is_within_filter_query_dict(field_name, aware_filter_date, now)
|
||||
|
||||
|
||||
class DateIsWithinXWeeksViewFilterType(TimezoneAwareDateViewFilterType):
|
||||
"""
|
||||
Is within X weeks filter checks if the field value is between today's date
|
||||
and the number of weeks specified in the filter.
|
||||
|
||||
The value of the filter is expected to be a string like "Europe/Rome?1". It
|
||||
uses character ? as separator between the timezone and the number of days.
|
||||
"""
|
||||
|
||||
type = "date_within_weeks"
|
||||
|
||||
def get_filter_date(
|
||||
self, filter_value: str, timezone: pytz.BaseTzInfo
|
||||
) -> Union[datetime, date]:
|
||||
"""
|
||||
Since zero weeks can be confusing, we raise an error if the number of
|
||||
weeks is 0. One week means today + 1 week inclusive, and so on.
|
||||
"""
|
||||
|
||||
number_of_weeks = int(filter_value)
|
||||
if number_of_weeks == 0:
|
||||
raise ValueError("Number of weeks cannot be 0")
|
||||
|
||||
filter_date = datetime.now(tz=timezone) + relativedelta(weeks=number_of_weeks)
|
||||
return filter_date.date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return get_is_within_filter_query_dict(field_name, aware_filter_date, now)
|
||||
|
||||
|
||||
class DateIsWithinXMonthsViewFilterType(TimezoneAwareDateViewFilterType):
|
||||
"""
|
||||
Is within X months filter checks if the field value is between today's date
|
||||
and the number of months specified in the filter.
|
||||
|
||||
The value of the filter is expected to be a string like "Europe/Rome?1". It
|
||||
uses character ? as separator between the timezone and the number of days.
|
||||
"""
|
||||
|
||||
type = "date_within_months"
|
||||
|
||||
def get_filter_date(
|
||||
self, filter_value: str, timezone: pytz.BaseTzInfo
|
||||
) -> Union[datetime, date]:
|
||||
"""
|
||||
Since zero months can be confusing, we raise an error if the number of
|
||||
months is 0. One month means today + 1 month inclusive, and so on. E.g.
|
||||
if today is 2023-01-31, within 1 month means within 2020-02-28
|
||||
inclusive.
|
||||
"""
|
||||
|
||||
number_of_months = int(filter_value)
|
||||
if number_of_months == 0:
|
||||
raise ValueError("Number of months cannot be 0")
|
||||
|
||||
filter_date = datetime.now(tz=timezone) + relativedelta(months=number_of_months)
|
||||
return filter_date.date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return get_is_within_filter_query_dict(field_name, aware_filter_date, now)
|
||||
|
||||
|
||||
class DateEqualsDaysAgoViewFilterType(TimezoneAwareDateViewFilterType):
|
||||
"""
|
||||
The "number of days ago" filter checks if the field value matches with today's
|
||||
|
@ -703,7 +821,7 @@ class DateEqualsDaysAgoViewFilterType(TimezoneAwareDateViewFilterType):
|
|||
return filter_date.date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {field_name: aware_filter_date}
|
||||
|
||||
|
@ -728,7 +846,7 @@ class DateEqualsMonthsAgoViewFilterType(TimezoneAwareDateViewFilterType):
|
|||
return filter_date.date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {
|
||||
f"{field_name}__year": aware_filter_date.year,
|
||||
|
@ -756,7 +874,7 @@ class DateEqualsYearsAgoViewFilterType(TimezoneAwareDateViewFilterType):
|
|||
return filter_date.date()
|
||||
|
||||
def get_filter_query_dict(
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime]
|
||||
self, field_name: str, aware_filter_date: Union[date, datetime], now: datetime
|
||||
) -> Dict:
|
||||
return {f"{field_name}__year": aware_filter_date.year}
|
||||
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import random
|
||||
from contextlib import contextmanager
|
||||
from datetime import date
|
||||
|
||||
from django.db.models import Q
|
||||
|
@ -6,6 +7,7 @@ from django.utils import timezone as django_timezone
|
|||
from django.utils.timezone import datetime, make_aware
|
||||
|
||||
import pytest
|
||||
import pytz
|
||||
from freezegun import freeze_time
|
||||
from pyinstrument import Profiler
|
||||
from pytest_unordered import unordered
|
||||
|
@ -5662,3 +5664,174 @@ def test_multiple_collaborators_has_filter_type_export_import(data_fixture):
|
|||
assert view_filter_type.set_import_serialized_value(user.email, {}) == ""
|
||||
assert view_filter_type.set_import_serialized_value("", id_mapping) == ""
|
||||
assert view_filter_type.set_import_serialized_value("wrong", id_mapping) == ""
|
||||
|
||||
|
||||
@contextmanager
|
||||
def rows_with_dates(data_fixture, dates, filter_type):
|
||||
user = data_fixture.create_user()
|
||||
table = data_fixture.create_database_table(user=user)
|
||||
grid_view = data_fixture.create_grid_view(table=table)
|
||||
date_field = data_fixture.create_date_field(table=table)
|
||||
|
||||
handler = ViewHandler()
|
||||
model = table.get_model()
|
||||
|
||||
rows = model.objects.bulk_create(
|
||||
[model(**{f"field_{date_field.id}": d}) for d in dates]
|
||||
)
|
||||
|
||||
view_filter = data_fixture.create_view_filter(
|
||||
view=grid_view,
|
||||
field=date_field,
|
||||
type=filter_type,
|
||||
value="",
|
||||
)
|
||||
|
||||
def get_filtered_row_ids():
|
||||
return [
|
||||
r.id for r in handler.apply_filters(grid_view, model.objects.all()).all()
|
||||
]
|
||||
|
||||
yield rows, view_filter, get_filtered_row_ids
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_date_within_days_view_filter(data_fixture):
|
||||
dates = [
|
||||
date(2020, 12, 1), # 0
|
||||
date(2021, 1, 1), # 1
|
||||
date(2021, 1, 2), # 2
|
||||
date(2021, 1, 3), # 3
|
||||
date(2021, 1, 4), # 4
|
||||
date(2021, 1, 5), # 5
|
||||
date(2021, 1, 6), # 6
|
||||
date(2021, 2, 1), # 7
|
||||
date(2022, 1, 1), # 8
|
||||
]
|
||||
|
||||
with rows_with_dates(data_fixture, dates, "date_within_days") as (
|
||||
rows,
|
||||
view_filter,
|
||||
get_filtered_row_ids,
|
||||
):
|
||||
with freeze_time("2021-01-02"):
|
||||
assert len(get_filtered_row_ids()) == len(dates)
|
||||
|
||||
view_filter.value = "1"
|
||||
view_filter.save()
|
||||
|
||||
with freeze_time("2021-01-02"):
|
||||
assert get_filtered_row_ids() == [rows[2].id, rows[3].id]
|
||||
|
||||
view_filter.value = "Europe/Rome?1"
|
||||
view_filter.save()
|
||||
|
||||
with freeze_time("2021-01-04"):
|
||||
assert get_filtered_row_ids() == [rows[4].id, rows[5].id]
|
||||
|
||||
with freeze_time(
|
||||
datetime(2021, 1, 4, 0, 0, 0, tzinfo=pytz.timezone("Pacific/Apia"))
|
||||
):
|
||||
assert get_filtered_row_ids() == [rows[3].id, rows[4].id]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_date_within_weeks_view_filter(data_fixture):
|
||||
dates = [
|
||||
date(2020, 12, 1), # 0
|
||||
date(2021, 1, 1), # 1
|
||||
date(2021, 1, 7), # 2
|
||||
date(2021, 1, 8), # 3
|
||||
date(2021, 1, 14), # 4
|
||||
date(2021, 1, 15), # 5
|
||||
date(2021, 1, 18), # 6
|
||||
date(2021, 2, 1), # 7
|
||||
date(2022, 1, 1), # 8
|
||||
]
|
||||
|
||||
with rows_with_dates(data_fixture, dates, "date_within_weeks") as (
|
||||
rows,
|
||||
view_filter,
|
||||
get_filtered_row_ids,
|
||||
):
|
||||
with freeze_time("2021-01-02"):
|
||||
assert len(get_filtered_row_ids()) == len(dates)
|
||||
|
||||
view_filter.value = "1"
|
||||
view_filter.save()
|
||||
|
||||
with freeze_time("2021-01-01"):
|
||||
assert get_filtered_row_ids() == [rows[1].id, rows[2].id, rows[3].id]
|
||||
|
||||
view_filter.value = "Europe/Rome?2"
|
||||
view_filter.save()
|
||||
|
||||
with freeze_time("2021-01-08"):
|
||||
assert get_filtered_row_ids() == [
|
||||
rows[3].id,
|
||||
rows[4].id,
|
||||
rows[5].id,
|
||||
rows[6].id,
|
||||
]
|
||||
|
||||
with freeze_time(
|
||||
datetime(2021, 1, 8, 0, 0, 0, tzinfo=pytz.timezone("Pacific/Apia"))
|
||||
):
|
||||
assert get_filtered_row_ids() == [
|
||||
rows[2].id,
|
||||
rows[3].id,
|
||||
rows[4].id,
|
||||
rows[5].id,
|
||||
rows[6].id,
|
||||
]
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_date_within_months_view_filter(data_fixture):
|
||||
dates = [
|
||||
date(2020, 12, 1), # 0
|
||||
date(2021, 1, 1), # 1
|
||||
date(2021, 1, 7), # 2
|
||||
date(2021, 1, 31), # 3
|
||||
date(2021, 2, 1), # 4
|
||||
date(2021, 2, 15), # 5
|
||||
date(2021, 3, 1), # 6
|
||||
date(2021, 3, 21), # 7
|
||||
date(2022, 1, 1), # 8
|
||||
]
|
||||
|
||||
with rows_with_dates(data_fixture, dates, "date_within_months") as (
|
||||
rows,
|
||||
view_filter,
|
||||
get_filtered_row_ids,
|
||||
):
|
||||
with freeze_time("2021-01-02"):
|
||||
assert len(get_filtered_row_ids()) == len(dates)
|
||||
|
||||
view_filter.value = "1"
|
||||
view_filter.save()
|
||||
|
||||
with freeze_time("2021-01-02"):
|
||||
assert get_filtered_row_ids() == [rows[2].id, rows[3].id, rows[4].id]
|
||||
|
||||
view_filter.value = "Europe/Rome?2"
|
||||
view_filter.save()
|
||||
|
||||
with freeze_time("2021-01-08"):
|
||||
assert get_filtered_row_ids() == [
|
||||
rows[3].id,
|
||||
rows[4].id,
|
||||
rows[5].id,
|
||||
rows[6].id,
|
||||
]
|
||||
|
||||
with freeze_time(
|
||||
datetime(2021, 1, 8, 0, 0, 0, tzinfo=pytz.timezone("Pacific/Apia"))
|
||||
):
|
||||
assert get_filtered_row_ids() == [
|
||||
rows[2].id,
|
||||
rows[3].id,
|
||||
rows[4].id,
|
||||
rows[5].id,
|
||||
rows[6].id,
|
||||
]
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
{
|
||||
"type": "feature",
|
||||
"message": "Add new view filter type for dates 'is within x days', 'is within x weeks' and 'is within x months'",
|
||||
"issue_number": 1094,
|
||||
"bullet_points": [],
|
||||
"created_at": "2023-05-16"
|
||||
}
|
|
@ -157,6 +157,9 @@
|
|||
"inThisWeek": "in this week",
|
||||
"inThisMonth": "in this month",
|
||||
"inThisYear": "in this year",
|
||||
"isWithinDays": "is within days",
|
||||
"isWithinWeeks": "is within weeks",
|
||||
"isWithinMonths": "is within months",
|
||||
"lowerThan": "lower than",
|
||||
"lengthIsLowerThan": "length is lower than",
|
||||
"hasFileType": "has file type"
|
||||
|
|
|
@ -45,6 +45,9 @@ import {
|
|||
DateEqualsTodayViewFilterType,
|
||||
DateBeforeTodayViewFilterType,
|
||||
DateAfterTodayViewFilterType,
|
||||
DateWithinDaysViewFilterType,
|
||||
DateWithinWeeksViewFilterType,
|
||||
DateWithinMonthsViewFilterType,
|
||||
DateEqualsDaysAgoViewFilterType,
|
||||
DateEqualsMonthsAgoViewFilterType,
|
||||
DateEqualsYearsAgoViewFilterType,
|
||||
|
@ -279,6 +282,18 @@ export default (context) => {
|
|||
'viewFilter',
|
||||
new DateAfterTodayViewFilterType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'viewFilter',
|
||||
new DateWithinDaysViewFilterType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'viewFilter',
|
||||
new DateWithinWeeksViewFilterType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'viewFilter',
|
||||
new DateWithinMonthsViewFilterType(context)
|
||||
)
|
||||
app.$registry.register(
|
||||
'viewFilter',
|
||||
new DateEqualsDaysAgoViewFilterType(context)
|
||||
|
|
|
@ -821,7 +821,7 @@ export class DateEqualsCurrentYearViewFilterType extends DateEqualsTodayViewFilt
|
|||
/**
|
||||
* Base class for days, months, years ago filters.
|
||||
*/
|
||||
export class DateEqualsXAgoViewFilterType extends LocalizedDateViewFilterType {
|
||||
export class LocalizedDateCompareViewFilterType extends LocalizedDateViewFilterType {
|
||||
getInputComponent() {
|
||||
return ViewFilterTypeNumberWithTimeZone
|
||||
}
|
||||
|
@ -841,7 +841,7 @@ export class DateEqualsXAgoViewFilterType extends LocalizedDateViewFilterType {
|
|||
return `${tzone}${this.getSeparator()}${xAgo}`
|
||||
}
|
||||
|
||||
splitTimezoneAndXago(field, rawValue) {
|
||||
splitTimezoneAndXToCompare(field, rawValue) {
|
||||
const [timezone, value] = this.splitTimezoneAndValue(rawValue)
|
||||
|
||||
let filterValue = value
|
||||
|
@ -854,7 +854,10 @@ export class DateEqualsXAgoViewFilterType extends LocalizedDateViewFilterType {
|
|||
}
|
||||
|
||||
getValidNumberWithTimezone(rawValue, field) {
|
||||
const [timezone, filterValue] = this.splitTimezoneAndXago(field, rawValue)
|
||||
const [timezone, filterValue] = this.splitTimezoneAndXToCompare(
|
||||
field,
|
||||
rawValue
|
||||
)
|
||||
return `${timezone}${this.getSeparator()}${filterValue}`
|
||||
}
|
||||
|
||||
|
@ -866,11 +869,11 @@ export class DateEqualsXAgoViewFilterType extends LocalizedDateViewFilterType {
|
|||
return this.getValidNumberWithTimezone(value, field)
|
||||
}
|
||||
|
||||
getDateToCompare(xAgo) {
|
||||
getDateToCompare(xToCompare) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare) {
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
throw new Error('Not implemented')
|
||||
}
|
||||
|
||||
|
@ -879,24 +882,107 @@ export class DateEqualsXAgoViewFilterType extends LocalizedDateViewFilterType {
|
|||
rowValue = ''
|
||||
}
|
||||
|
||||
const [timezone, xAgo] = this.splitTimezoneAndXago(field, filterValue)
|
||||
const [timezone, xToCompare] = this.splitTimezoneAndXToCompare(
|
||||
field,
|
||||
filterValue
|
||||
)
|
||||
|
||||
// an invalid daysAgo will result in an empty filter
|
||||
if (xAgo === '') {
|
||||
if (xToCompare === '') {
|
||||
return true
|
||||
}
|
||||
|
||||
const dateToCompare = this.getDateToCompare(xAgo)
|
||||
let dateToCompare
|
||||
try {
|
||||
dateToCompare = this.getDateToCompare(xToCompare)
|
||||
} catch (e) {
|
||||
return false
|
||||
}
|
||||
|
||||
const rowDate = moment.utc(rowValue)
|
||||
const today = moment.utc()
|
||||
if (timezone) {
|
||||
dateToCompare.tz(timezone)
|
||||
rowDate.tz(timezone)
|
||||
today.tz(timezone)
|
||||
}
|
||||
return this.isDateMatching(rowDate, dateToCompare)
|
||||
return this.isDateMatching(rowDate, dateToCompare, today)
|
||||
}
|
||||
}
|
||||
|
||||
export class DateEqualsDaysAgoViewFilterType extends DateEqualsXAgoViewFilterType {
|
||||
function isRowValueBetweenDays(rowValue, dateToCompare, today) {
|
||||
const [firstDay, lastDay] = dateToCompare.isSameOrBefore(today)
|
||||
? [dateToCompare, today]
|
||||
: [today, dateToCompare]
|
||||
return rowValue.isBetween(firstDay, lastDay, 'days', '[]')
|
||||
}
|
||||
|
||||
export class DateWithinDaysViewFilterType extends LocalizedDateCompareViewFilterType {
|
||||
static getType() {
|
||||
return 'date_within_days'
|
||||
}
|
||||
|
||||
getName() {
|
||||
const { i18n } = this.app
|
||||
return i18n.t('viewFilter.isWithinDays')
|
||||
}
|
||||
|
||||
getDateToCompare(xToCompare) {
|
||||
return moment.utc().add(parseInt(xToCompare), 'days')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
return isRowValueBetweenDays(rowValue, dateToCompare, today)
|
||||
}
|
||||
}
|
||||
|
||||
export class DateWithinWeeksViewFilterType extends LocalizedDateCompareViewFilterType {
|
||||
static getType() {
|
||||
return 'date_within_weeks'
|
||||
}
|
||||
|
||||
getName() {
|
||||
const { i18n } = this.app
|
||||
return i18n.t('viewFilter.isWithinWeeks')
|
||||
}
|
||||
|
||||
getDateToCompare(xToCompare) {
|
||||
const numberOfWeeks = parseInt(xToCompare)
|
||||
if (numberOfWeeks === 0) {
|
||||
throw new Error('Number of weeks cannot be 0')
|
||||
}
|
||||
return moment.utc().add(numberOfWeeks, 'weeks')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
return isRowValueBetweenDays(rowValue, dateToCompare, today)
|
||||
}
|
||||
}
|
||||
|
||||
export class DateWithinMonthsViewFilterType extends LocalizedDateCompareViewFilterType {
|
||||
static getType() {
|
||||
return 'date_within_months'
|
||||
}
|
||||
|
||||
getName() {
|
||||
const { i18n } = this.app
|
||||
return i18n.t('viewFilter.isWithinMonths')
|
||||
}
|
||||
|
||||
getDateToCompare(xToCompare) {
|
||||
const numberOfMonths = parseInt(xToCompare)
|
||||
if (numberOfMonths === 0) {
|
||||
throw new Error('Number of months cannot be 0')
|
||||
}
|
||||
return moment.utc().add(numberOfMonths, 'month')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
return isRowValueBetweenDays(rowValue, dateToCompare, today)
|
||||
}
|
||||
}
|
||||
|
||||
export class DateEqualsDaysAgoViewFilterType extends LocalizedDateCompareViewFilterType {
|
||||
static getType() {
|
||||
return 'date_equals_days_ago'
|
||||
}
|
||||
|
@ -906,16 +992,16 @@ export class DateEqualsDaysAgoViewFilterType extends DateEqualsXAgoViewFilterTyp
|
|||
return i18n.t('viewFilter.isDaysAgo')
|
||||
}
|
||||
|
||||
getDateToCompare(xAgo) {
|
||||
return moment.utc().subtract(parseInt(xAgo), 'days')
|
||||
getDateToCompare(xToCompare) {
|
||||
return moment.utc().subtract(parseInt(xToCompare), 'days')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare) {
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
return rowValue.isSame(dateToCompare, 'day')
|
||||
}
|
||||
}
|
||||
|
||||
export class DateEqualsMonthsAgoViewFilterType extends DateEqualsXAgoViewFilterType {
|
||||
export class DateEqualsMonthsAgoViewFilterType extends LocalizedDateCompareViewFilterType {
|
||||
static getType() {
|
||||
return 'date_equals_months_ago'
|
||||
}
|
||||
|
@ -925,16 +1011,16 @@ export class DateEqualsMonthsAgoViewFilterType extends DateEqualsXAgoViewFilterT
|
|||
return i18n.t('viewFilter.isMonthsAgo')
|
||||
}
|
||||
|
||||
getDateToCompare(xAgo) {
|
||||
return moment.utc().subtract(parseInt(xAgo), 'months')
|
||||
getDateToCompare(xToCompare) {
|
||||
return moment.utc().subtract(parseInt(xToCompare), 'months')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare) {
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
return rowValue.isSame(dateToCompare, 'month')
|
||||
}
|
||||
}
|
||||
|
||||
export class DateEqualsYearsAgoViewFilterType extends DateEqualsXAgoViewFilterType {
|
||||
export class DateEqualsYearsAgoViewFilterType extends LocalizedDateCompareViewFilterType {
|
||||
static getType() {
|
||||
return 'date_equals_years_ago'
|
||||
}
|
||||
|
@ -944,11 +1030,11 @@ export class DateEqualsYearsAgoViewFilterType extends DateEqualsXAgoViewFilterTy
|
|||
return i18n.t('viewFilter.isYearsAgo')
|
||||
}
|
||||
|
||||
getDateToCompare(xAgo) {
|
||||
return moment.utc().subtract(parseInt(xAgo), 'years')
|
||||
getDateToCompare(xToCompare) {
|
||||
return moment.utc().subtract(parseInt(xToCompare), 'years')
|
||||
}
|
||||
|
||||
isDateMatching(rowValue, dateToCompare) {
|
||||
isDateMatching(rowValue, dateToCompare, today) {
|
||||
return rowValue.isSame(dateToCompare, 'year')
|
||||
}
|
||||
}
|
||||
|
|
|
@ -8,6 +8,9 @@ import {
|
|||
DateEqualViewFilterType,
|
||||
DateNotEqualViewFilterType,
|
||||
DateEqualsTodayViewFilterType,
|
||||
DateWithinDaysViewFilterType,
|
||||
DateWithinWeeksViewFilterType,
|
||||
DateWithinMonthsViewFilterType,
|
||||
DateEqualsDaysAgoViewFilterType,
|
||||
DateEqualsMonthsAgoViewFilterType,
|
||||
DateEqualsYearsAgoViewFilterType,
|
||||
|
@ -387,6 +390,135 @@ const dateInThisYear = [
|
|||
},
|
||||
]
|
||||
|
||||
const dateWithinDays = [
|
||||
{
|
||||
rowValue: moment().tz('Europe/Berlin').add(1, 'days').format(),
|
||||
filterValue: 'Europe/Berlin?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: '1970-08-11T23:30:37.940086Z',
|
||||
filterValue: 'Europe/Berlin?2',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().add(2, 'days').format(),
|
||||
filterValue: 'UTC?3',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: '?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: 'Mars/Noland?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: '?',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().subtract(1, 'days').format(),
|
||||
filterValue: '?',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().subtract(1, 'days').format(),
|
||||
filterValue: '',
|
||||
expected: true,
|
||||
},
|
||||
]
|
||||
|
||||
const dateWithinWeeks = [
|
||||
{
|
||||
rowValue: moment().tz('Europe/Berlin').add(5, 'days').format(),
|
||||
filterValue: 'Europe/Berlin?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: '1970-08-11T23:30:37.940086Z',
|
||||
filterValue: 'Europe/Berlin?2',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().add(20, 'days').format(),
|
||||
filterValue: 'UTC?3',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: '?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: 'Mars/Noland?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: '?',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().subtract(1, 'days').format(),
|
||||
filterValue: '?',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().subtract(1, 'days').format(),
|
||||
filterValue: '',
|
||||
expected: true,
|
||||
},
|
||||
]
|
||||
|
||||
const dateWithinMonths = [
|
||||
{
|
||||
rowValue: moment().tz('Europe/Berlin').add(20, 'days').format(),
|
||||
filterValue: 'Europe/Berlin?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: '1970-08-11T23:30:37.940086Z',
|
||||
filterValue: 'Europe/Berlin?2',
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().add(80, 'days').format(),
|
||||
filterValue: 'UTC?3',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: '?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: 'Mars/Noland?1',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().format(),
|
||||
filterValue: '?',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().subtract(1, 'days').format(),
|
||||
filterValue: '?',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
rowValue: moment().utc().subtract(1, 'days').format(),
|
||||
filterValue: '',
|
||||
expected: true,
|
||||
},
|
||||
]
|
||||
|
||||
const dateDaysAgo = [
|
||||
{
|
||||
rowValue: moment().tz('Europe/Berlin').subtract(1, 'days').format(),
|
||||
|
@ -786,6 +918,30 @@ describe('All Tests', () => {
|
|||
expect(result).toBe(values.expected)
|
||||
})
|
||||
|
||||
test.each(dateWithinDays)('DateWithinDays', (values) => {
|
||||
const result = new DateWithinDaysViewFilterType({
|
||||
app: testApp,
|
||||
}).matches(values.rowValue, values.filterValue, {})
|
||||
if (result !== values.expected) console.log('days', values)
|
||||
expect(result).toBe(values.expected)
|
||||
})
|
||||
|
||||
test.each(dateWithinWeeks)('DateWithinWeeks', (values) => {
|
||||
const result = new DateWithinWeeksViewFilterType({
|
||||
app: testApp,
|
||||
}).matches(values.rowValue, values.filterValue, {})
|
||||
if (result !== values.expected) console.log('weeks', values)
|
||||
expect(result).toBe(values.expected)
|
||||
})
|
||||
|
||||
test.each(dateWithinMonths)('DateWithinMonths', (values) => {
|
||||
const result = new DateWithinMonthsViewFilterType({
|
||||
app: testApp,
|
||||
}).matches(values.rowValue, values.filterValue, {})
|
||||
if (result !== values.expected) console.log('months', values)
|
||||
expect(result).toBe(values.expected)
|
||||
})
|
||||
|
||||
test.each(dateDaysAgo)('DateDaysAgo', (values) => {
|
||||
const result = new DateEqualsDaysAgoViewFilterType({
|
||||
app: testApp,
|
||||
|
|
Loading…
Add table
Reference in a new issue