Skip to content

Commit

Permalink
fix date filtering for all day events (#1782)
Browse files Browse the repository at this point in the history
* fix date filtering for all day events

when making a query also do a query for all_day events
using only date part of the given date, but it must be
first converted to local time using client timezone so
it will the date user sees in the UI.

also tweak the way the all_day events are assigned to
dates in the UI based on newsroom code.

SDCP-682
  • Loading branch information
petrjasek authored Apr 14, 2023
1 parent 8e67be3 commit 9f0ab26
Show file tree
Hide file tree
Showing 23 changed files with 363 additions and 212 deletions.
2 changes: 2 additions & 0 deletions client/api/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {ISearchAPIParams, ISearchParams} from '../interfaces';
import {superdeskApi} from '../superdeskApi';
import {IRestApiResponse} from 'superdesk-api';
import {getDateTimeElasticFormat, getTimeZoneOffset} from '../utils';
import {default as timeUtils} from '../utils/time';


export function cvsToString(items?: Array<{[key: string]: any}>, field: string = 'qcode'): string {
Expand Down Expand Up @@ -48,6 +49,7 @@ export function convertCommonParams(params: ISearchParams): Partial<ISearchAPIPa
sort_order: params.sort_order,
sort_field: params.sort_field,
tz_offset: params.date_filter ? getTimeZoneOffset() : null,
time_zone: timeUtils.localTimeZone(),
};
}

Expand Down
5 changes: 2 additions & 3 deletions client/components/Events/EventDateTime.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
import React from 'react';
import moment from 'moment';

import {superdeskApi} from '../../superdeskApi';
import {IEventItem} from '../../interfaces';
Expand All @@ -20,8 +19,8 @@ export class EventDateTime extends React.PureComponent<IProps> {
render() {
const {gettext} = superdeskApi.localization;
const {item, ignoreAllDay, displayLocalTimezone} = this.props;
const start = moment(item.dates.start);
const end = moment(item.dates.end);
const start = eventUtils.getStartDate(item);
const end = eventUtils.getEndDate(item);
const isAllDay = eventUtils.isEventAllDay(start, end);
const multiDay = !eventUtils.isEventSameDay(start, end);
const isRemoteTimeZone = timeUtils.isEventInDifferentTimeZone(item);
Expand Down
2 changes: 2 additions & 0 deletions client/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1264,6 +1264,7 @@ export interface ISearchParams {
item_ids?: Array<string>;
name?: string;
tz_offset?: string;
time_zone?: string;
full_text?: string;
anpa_category?: Array<IANPACategory>;
subject?: Array<ISubject>;
Expand Down Expand Up @@ -1326,6 +1327,7 @@ export interface ISearchAPIParams {
item_ids?: string;
name?: string;
tz_offset?: string;
time_zone?: string;
full_text?: string;
anpa_category?: string;
subject?: string;
Expand Down
62 changes: 40 additions & 22 deletions client/utils/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -741,37 +741,53 @@ const getFlattenedEventsByDate = (events, startDate, endDate) => {
return flatten(sortBy(eventsList, [(e) => (e.date)]).map((e) => e.events.map((k) => [e.date, k._id])));
};


const getStartDate = (event: IEventItem) => (
event.dates?.all_day ? moment.utc(event.dates.start) : moment(event.dates?.start)
);

const getEndDate = (event: IEventItem) => (
event.dates?.all_day ? moment.utc(event.dates.end) : moment(event.dates?.end)
);

/*
* Groups the events by date
*/
const getEventsByDate = (events, startDate, endDate) => {
const getEventsByDate = (events: Array<IEventItem>, startDate, endDate) => {
if (!get(events, 'length', 0)) return [];
// check if search exists
// order by date
let sortedEvents = events.sort((a, b) => a.dates.start - b.dates.start);
let maxStartDate = sortedEvents[sortedEvents.length - 1].dates.start;
let sortedEvents = events.sort((a, b) => {
const startA = getStartDate(a);
const startB = getStartDate(b);

return startA.diff(startB);
});

let maxStartDate = getStartDate(sortedEvents[sortedEvents.length - 1]);

if (startDate.isAfter(maxStartDate, 'day')) {
maxStartDate = startDate;
}

const days = {};

function addEventToDate(event, date) {
let eventDate = date || event.dates.start;
let eventStart = event.dates.start;
let eventEnd = event.dates.end;
function addEventToDate(event, date?: moment.Moment) {
let eventDate = date || getStartDate(event);
let eventStart = getStartDate(event);
let eventEnd = getEndDate(event);

if (!event.dates.start.isSame(event.dates.end, 'day')) {
if (!eventStart.isSame(eventEnd, 'day') && !event.dates.all_day) {
eventStart = eventDate;
eventEnd = event.dates.end.isSame(eventDate, 'day') ?
event.dates.end : moment(eventDate.format('YYYY-MM-DD'), 'YYYY-MM-DD').add(86399, 'seconds');
eventEnd = eventEnd.isSame(eventDate, 'day') ?
eventEnd :
moment(eventDate.format('YYYY-MM-DD'), 'YYYY-MM-DD').add(86399, 'seconds');
}

if (!(isDateInRange(startDate, eventStart, eventEnd) ||
isDateInRange(endDate, eventStart, eventEnd))) {
if (!isDateInRange(eventStart, startDate, endDate) &&
!isDateInRange(eventEnd, startDate, endDate)) {
if (!(isDateInRange(startDate, eventStart, eventEnd, event.dates.all_day) ||
isDateInRange(endDate, eventStart, eventEnd, event.dates.all_day))) {
if (!isDateInRange(eventStart, startDate, endDate, event.dates.all_day) &&
!isDateInRange(eventEnd, startDate, endDate, event.dates.all_day)) {
return;
}
}
Expand All @@ -784,23 +800,23 @@ const getEventsByDate = (events, startDate, endDate) => {

let evt = cloneDeep(event);

evt._sortDate = eventDate;

evt._sortDate = eventStart;
days[eventDateFormatted].push(evt);
}

sortedEvents.forEach((event) => {
// compute the number of days of the event
let ending = event.actioned_date ? event.actioned_date : event.dates.end;
const ending = event.actioned_date ? event.actioned_date : getEndDate(event);
const startDate = getStartDate(event);

if (!event.dates.start.isSame(ending, 'day')) {
let deltaDays = Math.max(Math.ceil(ending.diff(event.dates.start, 'days', true)), 1);
if (!startDate.isSame(ending, 'day')) {
let deltaDays = Math.max(Math.ceil(ending.diff(startDate, 'days', true)), 1);
// if the event happens during more that one day, add it to every day
// add the event to the other days

for (let i = 1; i <= deltaDays; i++) {
// clone the date
const newDate = moment(event.dates.start.format('YYYY-MM-DD'), 'YYYY-MM-DD', true);
// clone the date
const newDate = moment(startDate.format('YYYY-MM-DD'), 'YYYY-MM-DD', true);

newDate.add(i, 'days');

Expand All @@ -812,7 +828,7 @@ const getEventsByDate = (events, startDate, endDate) => {

// add event to its initial starting date
// add an event only if it's not actioned or actioned after this event's start date
if (!event.actioned_date || event.actioned_date.isSameOrAfter(event.dates.start, 'date')) {
if (!event.actioned_date || event.actioned_date.isSameOrAfter(startDate, 'date')) {
addEventToDate(event);
}
});
Expand Down Expand Up @@ -1203,6 +1219,8 @@ const self = {
getFlattenedEventsByDate,
isEventCompleted,
fillEventTime,
getStartDate,
getEndDate,
};

export default self;
12 changes: 11 additions & 1 deletion client/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -672,11 +672,20 @@ export const onEventCapture = (event) => {
}
};

export const isDateInRange = (inputDate, startDate, endDate) => {
export const isDateInRange = (inputDate, startDate, endDate, allDay = false) => {
if (!inputDate) {
return false;
}

if (allDay) {
// if passed as string so inBetween will convert those to local dates
// from utc dates which are used for all day events
const startDay = startDate.format('YYYY-MM-DD');
const endDay = (endDate || inputDate).format('YYYY-MM-DD');

return moment(inputDate).isBetween(startDay, endDay, 'day', '[]');
}

if (startDate && moment(inputDate).isBefore(startDate, 'millisecond') ||
endDate && moment(inputDate).isSameOrAfter(endDate, 'millisecond')) {
return false;
Expand Down Expand Up @@ -945,6 +954,7 @@ export const sortBasedOnTBC = (days) => {
}

pushEventsForTheDay(days);

return sortBy(sortable, [(e) => (e.date)]);
};

Expand Down
3 changes: 2 additions & 1 deletion client/utils/search.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import {
SORT_ORDER,
} from '../interfaces';
import {MAIN} from '../constants';
import {getTimeZoneOffset} from './index';
import {getTimeZoneOffset, timeUtils} from './index';

function commonParamsToSearchParams(params: ICommonSearchParams<IEventOrPlanningItem>): ISearchParams {
return {
Expand All @@ -29,6 +29,7 @@ function commonParamsToSearchParams(params: ICommonSearchParams<IEventOrPlanning
lock_state: params.lock_state,
directly_locked: params.directly_locked,
tz_offset: params.timezoneOffset ?? getTimeZoneOffset(),
time_zone: timeUtils.localTimeZone(),
anpa_category: params.advancedSearch?.anpa_category,
date_filter: params.advancedSearch?.dates?.range,
start_date: params.advancedSearch?.dates?.start,
Expand Down
15 changes: 7 additions & 8 deletions e2e/cypress/e2e/events/event_action_create_planning.cy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import moment from 'moment-timezone';

import {setup, login, addItems, waitForPageLoad} from '../../support/common';
import {TIME_STRINGS} from '../../support/utils/time';
import {TIMEZONE} from '../../support/utils/time';
import {PlanningList, PlanningPreview, EventEditor, PlanningEditor} from '../../support/planning';

describe('Planning.Events: create planning action', () => {
Expand All @@ -16,10 +16,8 @@ describe('Planning.Events: create planning action', () => {

const expectedValues = {
slugline: 'Original',
'planning_date.date': '12/12/2045',
'planning_date.time': moment('2045-12-11' + TIME_STRINGS[0])
.tz('Australia/Sydney')
.format('HH:00'),
'planning_date.date': '12/12/2025',
'planning_date.time': '01:00',
description_text: 'Desc.',
ednote: 'Ed. Note',
anpa_category: ['Finance'],
Expand All @@ -28,6 +26,7 @@ describe('Planning.Events: create planning action', () => {

beforeEach(() => {
setup({fixture_profile: 'planning_prepopulate_data'}, '/#/planning');
const start = moment.tz("2025-12-12 01:00", TIMEZONE).utc();
addItems('events', [{
slugline: 'Original',
definition_short: 'Desc.',
Expand All @@ -37,9 +36,9 @@ describe('Planning.Events: create planning action', () => {
qcode: 'eocstat:eos5',
},
dates: {
start: '2045-12-11' + TIME_STRINGS[0],
end: '2045-12-11' + TIME_STRINGS[1],
tz: 'Australia/Sydney',
start: start.format("YYYY-MM-DDTHH:mm:ss+0000"),
end: start.add(1, 'h').format('YYYY-MM-DDTHH:mm:ss+0000'),
tz: TIMEZONE,
},
anpa_category: [{is_active: true, name: 'Finance', qcode: 'f', subject: '04000000'}],
subject: [{parent: '15000000', name: 'sports awards', qcode: '15103000'}],
Expand Down
4 changes: 2 additions & 2 deletions e2e/cypress/e2e/search/search_combined.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,8 +122,8 @@ describe('Search.Combined: searching events and planning', () => {
],
}, {
params: {
'start_date.date': '12/12/2025',
'end_date.date': '12/12/2025',
'start_date.date': '12/12/2045',
'end_date.date': '12/12/2045',
},
expectedCount: 0,
clearAfter: true,
Expand Down
4 changes: 2 additions & 2 deletions e2e/cypress/e2e/search/search_events.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,8 +156,8 @@ describe('Search.Events: searching events', () => {
]
}, {
params: {
'start_date.date': '12/12/2025',
'end_date.date': '12/12/2025',
'start_date.date': '12/12/2045',
'end_date.date': '12/12/2045',
},
expectedCount: 0,
clearAfter: true,
Expand Down
12 changes: 6 additions & 6 deletions e2e/cypress/e2e/search/search_filters.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ describe('Search.Filters: creating search filters', () => {
state: ['Postponed'],
only_posted: true,
lock_state: 'Locked',
'start_date.date': '12/12/2025',
'end_date.date': '12/12/2025',
'start_date.date': '12/12/2045',
'end_date.date': '12/12/2045',
calendars: ['Finance'],
agendas: ['Sports'],
});
Expand Down Expand Up @@ -78,8 +78,8 @@ describe('Search.Filters: creating search filters', () => {
state: ['Postponed'],
only_posted: true,
lock_state: 'Locked',
'start_date.date': '12/12/2025',
'end_date.date': '12/12/2025',
'start_date.date': '12/12/2045',
'end_date.date': '12/12/2045',
calendars: ['Sport'],
source: ['aap'],
location: 'Sydney Opera House',
Expand Down Expand Up @@ -131,8 +131,8 @@ describe('Search.Filters: creating search filters', () => {
state: ['Postponed'],
only_posted: true,
lock_state: 'Locked',
'start_date.date': '12/12/2025',
'end_date.date': '12/12/2025',
'start_date.date': '12/12/2045',
'end_date.date': '12/12/2045',
agendas: ['Sports'],
urgency: '3',
g2_content_type: 'Video',
Expand Down
4 changes: 2 additions & 2 deletions e2e/cypress/e2e/search/search_planning.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,8 @@ describe('Search.Planning: searching planning items', () => {
],
}, {
params: {
'start_date.date': '12/12/2025',
'end_date.date': '12/12/2025',
'start_date.date': '12/12/2045',
'end_date.date': '12/12/2045',
},
expectedCount: 0,
clearAfter: true,
Expand Down
Loading

0 comments on commit 9f0ab26

Please sign in to comment.