From fcae4e317f59d7baece7310f9f5e3c0c92a6f0d0 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Mon, 1 Jul 2024 12:57:05 +0300 Subject: [PATCH 1/6] Respect date filter in event list for multi day events (#2007) --- client/apps/Planning/PlanningList.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/client/apps/Planning/PlanningList.tsx b/client/apps/Planning/PlanningList.tsx index 3a2d7e932..86e1805ef 100644 --- a/client/apps/Planning/PlanningList.tsx +++ b/client/apps/Planning/PlanningList.tsx @@ -28,6 +28,7 @@ import {ITEM_TYPE} from '../../constants'; import {ListPanel} from '../../components/Main'; import {PlanningListSubNav} from './PlanningListSubNav'; +import moment from 'moment'; interface IProps { groups: Array<{ @@ -185,7 +186,17 @@ export class PlanningListComponent extends React.PureComponent { { + const dateFilter = currentSearch.advancedSearch?.dates?.start; + + if (dateFilter != null) { + return groups.filter((group) => + moment(group.date).isSameOrAfter(dateFilter), + ); + } + + return groups; + })()} onItemClick={openPreview} onDoubleClick={edit} agendas={agendas} From 9f94b19b6761f5577f4231ad8acea5f7425ba92b Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Wed, 3 Jul 2024 13:22:27 +0300 Subject: [PATCH 2/6] Respect date filter for today (#2014) --- client/apps/Planning/PlanningList.tsx | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/client/apps/Planning/PlanningList.tsx b/client/apps/Planning/PlanningList.tsx index 86e1805ef..79b81fde0 100644 --- a/client/apps/Planning/PlanningList.tsx +++ b/client/apps/Planning/PlanningList.tsx @@ -187,15 +187,11 @@ export class PlanningListComponent extends React.PureComponent { { - const dateFilter = currentSearch.advancedSearch?.dates?.start; + const dateFilter = currentSearch.advancedSearch?.dates?.start ?? moment().date(); - if (dateFilter != null) { - return groups.filter((group) => - moment(group.date).isSameOrAfter(dateFilter), - ); - } - - return groups; + return groups.filter((group) => + moment(group.date).isSameOrAfter(dateFilter), + ); })()} onItemClick={openPreview} onDoubleClick={edit} From 9fdacc23fac96b71bc02b1469f663293b6035254 Mon Sep 17 00:00:00 2001 From: Konstantin Markov Date: Fri, 5 Jul 2024 15:33:57 +0300 Subject: [PATCH 3/6] Date improvements (#2017) --- client/apps/Planning/PlanningList.tsx | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/client/apps/Planning/PlanningList.tsx b/client/apps/Planning/PlanningList.tsx index 79b81fde0..e7a0b3423 100644 --- a/client/apps/Planning/PlanningList.tsx +++ b/client/apps/Planning/PlanningList.tsx @@ -28,7 +28,6 @@ import {ITEM_TYPE} from '../../constants'; import {ListPanel} from '../../components/Main'; import {PlanningListSubNav} from './PlanningListSubNav'; -import moment from 'moment'; interface IProps { groups: Array<{ @@ -187,11 +186,17 @@ export class PlanningListComponent extends React.PureComponent { { - const dateFilter = currentSearch.advancedSearch?.dates?.start ?? moment().date(); + const dateFilter = currentSearch.advancedSearch?.dates?.start?.toDate() + ?? new Date(); - return groups.filter((group) => - moment(group.date).isSameOrAfter(dateFilter), - ); + dateFilter.setHours(0, 0, 0, 0); + + return groups.filter((group) => { + const dateStringToJSDate = new Date(group.date); + + dateStringToJSDate.setHours(0, 0, 0, 0); + return dateStringToJSDate >= dateFilter; + }); })()} onItemClick={openPreview} onDoubleClick={edit} From 800c47bf587ba66fb44946220627914a7775030c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Tue, 9 Jul 2024 09:04:16 +0200 Subject: [PATCH 4/6] reingest event on subject name change (#2020) * reingest event on subject name change SDCP-794 SDCP-795 * test different order --- server/planning/events/events.py | 19 ++++++++++++++----- server/planning/tests/events_service_test.py | 15 +++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/server/planning/events/events.py b/server/planning/events/events.py index 999fbd533..5364251cb 100644 --- a/server/planning/events/events.py +++ b/server/planning/events/events.py @@ -103,14 +103,23 @@ def get_coverage_id(coverage: EmbeddedCoverageItem) -> str: ] +def get_subject_str(subject: Dict[str, str]) -> str: + return ":".join( + [ + subject.get("name", ""), + subject.get("qcode", ""), + subject.get("scheme", ""), + subject.get("translations", ""), + ] + ) + + def is_event_updated(new_item: Event, old_item: Event) -> bool: if new_item.get("name") != old_item.get("name"): return True - new_subject = set([subject.get("qcode") for subject in new_item.get("subject", [])]) - old_subject = set([subject.get("qcode") for subject in old_item.get("subject", [])]) - if new_subject != old_subject: - return True - return False + new_subject = set([get_subject_str(subject) for subject in new_item.get("subject", [])]) + old_subject = set([get_subject_str(subject) for subject in old_item.get("subject", [])]) + return new_subject != old_subject class EventsService(superdesk.Service): diff --git a/server/planning/tests/events_service_test.py b/server/planning/tests/events_service_test.py index daa72b041..9dce29300 100644 --- a/server/planning/tests/events_service_test.py +++ b/server/planning/tests/events_service_test.py @@ -17,11 +17,26 @@ def test_is_new_version(): assert not service.is_new_version(new_event, old_event) + new_event["subject"] = [{"qcode": "foo"}, {"qcode": "bar"}] + old_event["subject"] = [{"qcode": "bar"}, {"qcode": "foo"}] + + assert not service.is_new_version(new_event, old_event) + new_event["subject"] = [{"qcode": "foo"}] old_event["subject"] = [{"qcode": "bar"}] assert service.is_new_version(new_event, old_event) + new_event["subject"] = [{"qcode": "foo", "name": "Foo"}] + old_event["subject"] = [{"qcode": "foo", "name": "foo"}] + + assert service.is_new_version(new_event, old_event) + + new_event["subject"] = [{}] + old_event["subject"] = [{"qcode": "foo", "name": "foo"}] + + assert service.is_new_version(new_event, old_event) + def test_should_update(): service = EventsService() From 4d19a6fbee97d38b030484973fb106b68e5d3f7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Tue, 9 Jul 2024 17:20:13 +0200 Subject: [PATCH 5/6] fix `events.is_new_version` when translations are None (#2022) SDCP-794 SDCP-795 --- server/planning/events/events.py | 2 +- server/planning/tests/events_service_test.py | 10 ++++++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/server/planning/events/events.py b/server/planning/events/events.py index 5364251cb..b6f41bbc3 100644 --- a/server/planning/events/events.py +++ b/server/planning/events/events.py @@ -109,7 +109,7 @@ def get_subject_str(subject: Dict[str, str]) -> str: subject.get("name", ""), subject.get("qcode", ""), subject.get("scheme", ""), - subject.get("translations", ""), + str(subject.get("translations", "")), ] ) diff --git a/server/planning/tests/events_service_test.py b/server/planning/tests/events_service_test.py index 9dce29300..77cc09a1a 100644 --- a/server/planning/tests/events_service_test.py +++ b/server/planning/tests/events_service_test.py @@ -37,6 +37,16 @@ def test_is_new_version(): assert service.is_new_version(new_event, old_event) + new_event["subject"] = [{"qcode": "foo", "name": "foo", "translations": {"fr-CA": "Foo"}}] + old_event["subject"] = [{"qcode": "foo", "name": "foo", "translations": None}] + + assert service.is_new_version(new_event, old_event) + + new_event["subject"] = [{"qcode": "foo", "name": "foo", "translations": {"fr-CA": "Bar"}}] + old_event["subject"] = [{"qcode": "foo", "name": "foo", "translations": {"fr-CA": "Foo"}}] + + assert service.is_new_version(new_event, old_event) + def test_should_update(): service = EventsService() From cc220b8a4f36eff06a3222f8bf82744070ff6789 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Ja=C5=A1ek?= Date: Tue, 16 Jul 2024 09:20:58 +0200 Subject: [PATCH 6/6] fix date filtering (#2024) * fix date filtering - always return ongoing events for selected date - check properly all_day / no_end_time flags SDESK-7334 * fix query for all day events there is no end time for all day events as well * add test --- server/features/search_events.feature | 50 +++++++++++++++ server/planning/search/queries/elastic.py | 74 ++++++++++++++++----- server/planning/search/queries/events.py | 78 ++++------------------- 3 files changed, 118 insertions(+), 84 deletions(-) diff --git a/server/features/search_events.feature b/server/features/search_events.feature index 71d98519d..000d7ebe8 100644 --- a/server/features/search_events.feature +++ b/server/features/search_events.feature @@ -326,3 +326,53 @@ Feature: Event Search ]} """ + @auth + Scenario: Filter by date using America/Toronto timezone + Given "events" + """ + [{ + "guid": "all_day_multi", + "name": "all day event multiday", + "dates": {"start": "2024-07-14T00:00:00+0000", "end": "2024-07-16T00:00:00+0000", "all_day": true} + }, { + "guid": "all_day_single", + "name": "all day single day", + "dates": {"start": "2024-07-15T00:00:00+0000", "end": "2024-07-15T00:00:00+0000", "all_day": true} + }, { + "guid": "no_end_time_multi", + "name": "no end time multiday", + "dates": {"start": "2024-07-13T10:00:00+0000", "end": "2024-07-15T00:00:00+0000", "no_end_time": true} + }, { + "guid": "no_end_time_single", + "name": "no end time single day", + "dates": {"start": "2024-07-15T10:00:00+0000", "end": "2024-07-15T10:00:00+0000", "no_end_time": true} + }, { + "guid": "matching", + "name": "regular", + "dates": {"start": "2024-07-15T10:00:00+0000", "end": "2024-07-16T00:00:00+0000"} + }, + { + "guid": "not matching", + "name": "not matching", + "dates": {"start": "2024-07-01T10:00:00+0000", "end": "2024-07-02T00:00:00+0000"} + } + ] + """ + When we get "/events_planning_search?repo=events&only_future=false&time_zone=America/Toronto&start_date=2024-07-15T04:00:00" + Then we get list with 5 items + """ + {"_items": [ + {"guid": "all_day_multi"}, + {"guid": "all_day_single"}, + {"guid": "no_end_time_multi"}, + {"guid": "no_end_time_single"}, + {"guid": "matching"} + ]} + """ + When we get "/events_planning_search?repo=events&only_future=false&time_zone=America/Toronto&start_date=2024-07-16T04:00:00" + Then we get list with 1 items + """ + {"_items": [ + {"guid": "all_day_multi"} + ]} + """ diff --git a/server/planning/search/queries/elastic.py b/server/planning/search/queries/elastic.py index 523b19a3c..025b5a4e9 100644 --- a/server/planning/search/queries/elastic.py +++ b/server/planning/search/queries/elastic.py @@ -209,26 +209,66 @@ def field_range(query: ElasticRangeParams): local_params = params.copy() local_params.pop("time_zone", None) for key in ("gt", "gte", "lt", "lte"): - if local_params.get(key) and "T" in local_params[key] and query.time_zone: - tz = pytz.timezone(query.time_zone) + if local_params.get(key) and "T" in local_params[key] and params.get("time_zone"): + tz = pytz.timezone(params["time_zone"]) utc_value = datetime.fromisoformat(local_params[key].replace("+0000", "+00:00")) local_value = utc_value.astimezone(tz) local_params[key] = local_value.strftime("%Y-%m-%d") - return { - "bool": { - "should": [ - {"range": {query.field: params}}, - { - "bool": { - "must": [ - {"term": {"dates.all_day": True}}, - {"range": {query.field: local_params}}, - ], - } - }, - ], - }, - } + if query.field == "dates.start": + return { + "bool": { + "should": [ + { + "bool": { + "must_not": [ + {"term": {"dates.all_day": True}}, + ], + "must": [ + {"range": {query.field: params}}, + ], + }, + }, + { + "bool": { + "must": [ + {"term": {"dates.all_day": True}}, + {"range": {query.field: local_params}}, + ], + }, + }, + ], + }, + } + else: + return { + "bool": { + "should": [ + { + "bool": { + "must_not": [ + {"term": {"dates.all_day": True}}, + {"term": {"dates.no_end_time": True}}, + ], + "must": [ + {"range": {query.field: params}}, + ], + }, + }, + { + "bool": { + "should": [ + {"term": {"dates.all_day": True}}, + {"term": {"dates.no_end_time": True}}, + ], + "must": [ + {"range": {query.field: local_params}}, + ], + "minimum_should_match": 1, + }, + }, + ], + }, + } return {"range": {query.field: params}} diff --git a/server/planning/search/queries/events.py b/server/planning/search/queries/events.py index 5502a4d1b..1ed8785ef 100644 --- a/server/planning/search/queries/events.py +++ b/server/planning/search/queries/events.py @@ -255,16 +255,7 @@ def search_date_start(params: Dict[str, Any], query: elastic.ElasticQuery): if not date_filter and start_date and not end_date: query.filter.append( - elastic.bool_or( - [ - elastic.date_range( - elastic.ElasticRangeParams(field="dates.start", gte=start_date, time_zone=time_zone) - ), - elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", gte=start_date, time_zone=time_zone) - ), - ] - ) + elastic.date_range(elastic.ElasticRangeParams(field="dates.end", gte=start_date, time_zone=time_zone)), ) @@ -273,16 +264,7 @@ def search_date_end(params: Dict[str, Any], query: elastic.ElasticQuery): if not date_filter and not start_date and end_date: query.filter.append( - elastic.bool_or( - [ - elastic.date_range( - elastic.ElasticRangeParams(field="dates.start", lte=end_date, time_zone=time_zone) - ), - elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", lte=end_date, time_zone=time_zone) - ), - ] - ) + elastic.date_range(elastic.ElasticRangeParams(field="dates.start", lte=end_date, time_zone=time_zone)), ) @@ -291,55 +273,17 @@ def search_date_range(params: Dict[str, Any], query: elastic.ElasticQuery): if not date_filter and start_date and end_date: query.filter.append( - elastic.bool_or( + elastic.bool_and( [ - elastic.bool_and( - [ - elastic.date_range( - elastic.ElasticRangeParams( - field="dates.start", - gte=start_date, - time_zone=time_zone, - ) - ), - elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", lte=end_date, time_zone=time_zone) - ), - ] - ), - elastic.bool_and( - [ - elastic.date_range( - elastic.ElasticRangeParams( - field="dates.start", - lt=start_date, - time_zone=time_zone, - ) - ), - elastic.date_range( - elastic.ElasticRangeParams(field="dates.end", gt=end_date, time_zone=time_zone) - ), - ] + elastic.date_range( + elastic.ElasticRangeParams( + field="dates.start", + gte=end_date, + time_zone=time_zone, + ), ), - elastic.bool_or( - [ - elastic.date_range( - elastic.ElasticRangeParams( - field="dates.start", - gte=start_date, - lte=end_date, - time_zone=time_zone, - ) - ), - elastic.date_range( - elastic.ElasticRangeParams( - field="dates.end", - gte=start_date, - lte=end_date, - time_zone=time_zone, - ) - ), - ] + elastic.date_range( + elastic.ElasticRangeParams(field="dates.end", lte=start_date, time_zone=time_zone) ), ] )