Skip to content

Commit

Permalink
Remove use of recurrence in opfab (#7499)
Browse files Browse the repository at this point in the history
Signed-off-by: vlo-rte <[email protected]>
  • Loading branch information
vlo-rte committed Dec 26, 2024
1 parent c625bd1 commit 55c8a73
Show file tree
Hide file tree
Showing 30 changed files with 69 additions and 2,768 deletions.
11 changes: 0 additions & 11 deletions client/cards/src/main/avro/timeSpan.avdl
Original file line number Diff line number Diff line change
@@ -1,18 +1,7 @@
@namespace("org.opfab.avro")
protocol TimeSpanProtocl {
record HoursAndMinutes {
int hours;
int minutes;
}
record Recurrence {
HoursAndMinutes hoursAndMinutes;
union {null, array<int>} daysOfWeek = null;
union {null, string} timeZone = null;
union {null, int} durationInMinutes = null;
}
record TimeSpan {
timestamp_ms start;
union {null, timestamp_ms} end = null;
union {null, Recurrence} recurrence = null;
}
}
121 changes: 4 additions & 117 deletions node-services/cards-reminder/src/domain/application/reminderUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@
* This file is part of the OperatorFabric project.
*/

import {Card, Recurrence, HourAndMinutes, TimeSpan} from '../model/card.model';
import {add, getISODay} from 'date-fns';
import {fromZonedTime, toZonedTime} from 'date-fns-tz';
import {Card, TimeSpan} from '../model/card.model';

export function getNextTimeForRepeating(card: Card, startingDate?: number): number {
if (card.timeSpans != null) {
Expand All @@ -31,121 +29,10 @@ function getNextTimeForRepeatingFromTimeSpan(timeSpan: TimeSpan, startingDate?:
if (startingDate == null) {
startingDate = new Date().valueOf();
}
if (timeSpan.recurrence == null) {
if (timeSpan.start < startingDate) {
return -1;
} else {
return timeSpan.start;
}
} else if (startingDate > timeSpan.start) {
return getNextDateTimeFromRecurrence(startingDate, timeSpan.recurrence);
} else {
return getNextDateTimeFromRecurrence(timeSpan.start, timeSpan.recurrence);
}
}

function getNextDateTimeFromRecurrence(StartingDate: number, recurrence: Recurrence): number {
if (!isRecurrenceObjectInValidFormat(recurrence)) {
if (timeSpan.start < startingDate) {
return -1;
}

if (recurrence.timeZone == null || !isValidTimeZone(recurrence.timeZone)) {
recurrence.timeZone = 'Europe/Paris';
}

let nextDateTime = new Date(StartingDate);
nextDateTime = toZonedTime(nextDateTime, recurrence.timeZone);

const startingHoursMinutes = new HourAndMinutes(nextDateTime.getHours(), nextDateTime.getMinutes());
if (isFirstHoursMinutesInferiorOrEqualToSecondOne(recurrence.hoursAndMinutes, startingHoursMinutes)) {
nextDateTime = add(nextDateTime, {days: 1, hours: 0, minutes: 0});
}

nextDateTime = moveToValidMonth(nextDateTime, recurrence);

if (isDaysOfWeekFieldSet(recurrence)) {
if (recurrence?.daysOfWeek?.includes(getISODay(nextDateTime)) === false) {
// we keep the month found previously
const monthForNextDateTime = nextDateTime.getMonth();

nextDateTime.setHours(0);
nextDateTime.setMinutes(0);
do {
nextDateTime = add(nextDateTime, {days: 1});

if (nextDateTime.getMonth() !== monthForNextDateTime) {
// in case incrementing took us into the next month
nextDateTime = moveToValidMonth(nextDateTime, recurrence);
}
} while (!recurrence.daysOfWeek.includes(getISODay(nextDateTime)));

nextDateTime.setHours(recurrence.hoursAndMinutes.hours);
nextDateTime.setMinutes(recurrence.hoursAndMinutes.minutes);
nextDateTime.setSeconds(0);
nextDateTime.setMilliseconds(0);
nextDateTime = fromZonedTime(nextDateTime, recurrence.timeZone);

return nextDateTime.valueOf();
}
}

nextDateTime.setHours(recurrence.hoursAndMinutes.hours);
nextDateTime.setMinutes(recurrence.hoursAndMinutes.minutes);
nextDateTime.setSeconds(0);
nextDateTime.setMilliseconds(0);
nextDateTime = fromZonedTime(nextDateTime, recurrence.timeZone);
return nextDateTime.valueOf();
}

function isRecurrenceObjectInValidFormat(recurrence: Recurrence): boolean {
if (recurrence.months != null) {
for (const month of recurrence.months) {
if (month < 0 || month > 11) {
return false;
}
}
}

if (recurrence.daysOfWeek != null) {
for (const dayOfWeek of recurrence.daysOfWeek) {
if (dayOfWeek < 1 || dayOfWeek > 7) {
return false;
}
}
}
return true;
}

function moveToValidMonth(nextDateTime: Date, recurrence: Recurrence): Date {
if (
recurrence.months != null &&
recurrence.months.length > 0 &&
!recurrence.months.includes(nextDateTime.getMonth())
) {
do {
nextDateTime = add(nextDateTime, {months: 1});
nextDateTime.setDate(1);
} while (!recurrence.months.includes(nextDateTime.getMonth()));
}
return nextDateTime;
}

function isFirstHoursMinutesInferiorOrEqualToSecondOne(hm1: HourAndMinutes, hm2: HourAndMinutes): boolean {
if (hm1.hours < hm2.hours) return true;
if (hm1.hours > hm2.hours) return false;
return hm1.minutes <= hm2.minutes;
}

function isDaysOfWeekFieldSet(recurrence: Recurrence): boolean {
return recurrence.daysOfWeek != null && recurrence.daysOfWeek.length > 0;
}

function isValidTimeZone(tz): boolean {
try {
Intl.DateTimeFormat(undefined, {timeZone: tz});
return true;
//eslint-disable-next-line @typescript-eslint/no-unused-vars
} catch (ex) {
return false;
} else {
return timeSpan.start;
}
}
19 changes: 1 addition & 18 deletions node-services/cards-reminder/src/domain/model/card.model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,24 +22,7 @@ export class Card {
export class TimeSpan {
constructor(
readonly start: number,
readonly end?: number | null,
readonly recurrence?: Recurrence
) {}
}

export class Recurrence {
constructor(
public hoursAndMinutes: HourAndMinutes,
public daysOfWeek?: number[],
public timeZone?: string,
public months?: number[]
) {}
}

export class HourAndMinutes {
constructor(
public hours: number,
public minutes: number
readonly end?: number | null
) {}
}

Expand Down
190 changes: 1 addition & 189 deletions node-services/cards-reminder/src/tests/cardsReminderControl.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import 'jest';
import * as Logger from '../common/server-side/logger';
import ReminderService from '../domain/application/reminderService';
import {RRuleReminderService} from '../domain/application/rruleReminderService';
import {HourAndMinutes, RRule, Recurrence, TimeSpan, Day, Frequency} from '../domain/model/card.model';
import {RRule, TimeSpan, Day, Frequency} from '../domain/model/card.model';
import CardsReminderControl from '../domain/application/cardsReminderControl';
import {CardOperation, CardOperationType} from '../domain/model/card-operation.model';
import {RemindDatabaseServiceStub} from './remindDataBaseServiceStub';
Expand Down Expand Up @@ -295,194 +295,6 @@ describe('Cards reminder with rrule structure', function () {
});
});

describe('Cards reminder with recurrence structure', function () {
function getTestCard(): any {
const startDate = new Date('2017-01-01 01:00').valueOf();

const recurrence = new Recurrence(
new HourAndMinutes(2, 10),
[1, 2, 3, 4, 5, 6, 7],
'Europe/Paris',
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
);

const timespans = [new TimeSpan(startDate, null, recurrence)];

return {
uid: 'uid1',
id: 'id1',
secondsBeforeTimeSpanForReminder: 300,
timeSpans: timespans,
startDate
};
}

beforeEach(() => {
opfabServicesInterfaceStub.clean();
remindDatabaseServiceStub.clean();

jest.useFakeTimers();
setCurrentTime('2017-01-01 01:00');
});

afterAll(() => {
jest.useRealTimers();
});

it('GIVEN a card was sent WHEN current date (02:04) < remind date - secondsBeforeTimeSpanForReminder (02:05) THEN no remind is sent', async function () {
await sendCard(getTestCard());
setCurrentTime('2017-01-01 02:04');
await checkNoReminderIsSent();
});

it('GIVEN a card was sent WHEN current date (02:06) > remind date - secondsBeforeTimeSpanForReminder (02:05) THEN remind is sent', async function () {
await sendCard(getTestCard());
setCurrentTime('2017-01-01 02:06');
await checkOneReminderIsSent();
});

it('GIVEN a card was sent WHEN current date (02:11) > remind date - secondsBeforeTimeSpanForReminder , with secondsBeforeTimeSpanForReminder = 0 (02:10) THEN remind is sent', async function () {
const card = getTestCard();
card.secondsBeforeTimeSpanForReminder = 0;
await sendCard(card);
setCurrentTime('2017-01-01 02:11');
await checkOneReminderIsSent();
});

it('GIVEN two cards were sent WHEN current date is after reminds date THEN two reminds are sent', async function () {
const card1 = getTestCard();
const card2 = getTestCard();
card2.id = 'id2';
card2.uid = 'uid2';
await sendCard(card1);
await sendCard(card2);

setCurrentTime('2017-01-01 02:06');
await checkRemindersAreSent(['uid1', 'uid2']);
});

it('GIVEN reminder service was reset WHEN current date is after reminds date of two cards THEN two reminds are sent', async function () {
const card1 = getTestCard();
const card2 = getTestCard();
card2.id = 'id2';
card2.uid = 'uid2';
rruleRemindDatabaseServiceStub.addCard(card1);
rruleRemindDatabaseServiceStub.addCard(card2);
await cardsReminderControl.resetReminderDatabase();

setCurrentTime('2017-01-01 02:04');
await checkNoReminderIsSent();

setCurrentTime('2017-01-01 02:06');
await checkRemindersAreSent(['uid1', 'uid2']);
});

it('GIVEN a card was sent WHEN remind date > card endDate THEN no remind is sent', async function () {
const card = getTestCard();
card.endDate = new Date('2017-01-01 01:20').valueOf();
await sendCard(card);

setCurrentTime('2017-01-01 02:06');
await checkNoReminderIsSent();
});

it('GIVEN a card was sent WHEN remind date < card endDate THEN remind is sent', async function () {
const card = getTestCard();
card.endDate = new Date('2017-01-01 02:20').valueOf();
await sendCard(card);

setCurrentTime('2017-01-01 02:06');
await checkOneReminderIsSent();
});

it('GIVEN a card was sent WHEN remind is every day THEN remind is sent the first day and the second day', async function () {
await sendCard(getTestCard());

// First remind
setCurrentTime('2017-01-01 02:06');
await checkOneReminderIsSent();

// No new remind one hour later
setCurrentTime('2017-01-01 03:06');
await checkNoReminderIsSent();

// Second remind the day after
setCurrentTime('2017-01-02 02:06');
await checkOneReminderIsSent();
});

it('GIVEN a card was sent WHEN second remind date is after end date THEN only one remind is sent', async function () {
const card = getTestCard();
card.endDate = new Date('2017-01-02 01:00').valueOf();
await sendCard(card);

// First remind
setCurrentTime('2017-01-01 02:06');
await checkOneReminderIsSent();

// No remind 1 minutes later
setCurrentTime('2017-01-01 02:07');
await checkNoReminderIsSent();

// No remind the day after
setCurrentTime('2017-01-02 02:06');
await checkNoReminderIsSent();
});

it('GIVEN a card was send WHEN a new card version is sent THEN reminder is updated', async function () {
await sendCard(getTestCard());

setCurrentTime('2017-01-01 02:00');
await checkNoReminderIsSent();

const card = getTestCard();
card.uid = '0002';
card.timeSpans[0].start = new Date('2017-01-02 01:00').valueOf();

await sendCard(card);

setCurrentTime('2017-01-01 02:06');
await checkNoReminderIsSent();

setCurrentTime('2017-01-02 02:06');
await checkOneReminderIsSent('0002');
});

it('GIVEN a card WHEN card is deleted THEN reminder is removed', async function () {
await sendCard(getTestCard());
expect(remindDatabaseServiceStub.getNbReminder()).toBe(1);

const cardOperation: CardOperation = {
cardId: 'uid1',
card: getTestCard(),
type: CardOperationType.DELETE
};
const message = {
content: JSON.stringify(cardOperation)
};
await reminderService.onMessage(message);
expect(remindDatabaseServiceStub.getNbReminder()).toBe(0);
});

it('GIVEN a card is to be reminded WHEN card is not existing in database THEN reminder is removed', async function () {
await sendCard(getTestCard());
expect(remindDatabaseServiceStub.getNbReminder()).toBe(1);
remindDatabaseServiceStub.cleanCards();

setCurrentTime('2017-01-01 02:06');
await cardsReminderControl.checkCardsReminder();
expect(remindDatabaseServiceStub.getNbReminder()).toBe(0);
});

it('GIVEN a card WHEN card has no timezone value THEN reminder proceed with default value', async function () {
const card = getTestCard();
card.timeSpans[0].recurrence.timeZone = undefined;
await sendCard(card);
setCurrentTime('2017-01-01 02:06');
await checkOneReminderIsSent();
});
});

describe('Cards reminder with timespans and no recurrence', function () {
function getTestCard(): any {
const startDate = new Date('2017-01-01 02:00').valueOf();
Expand Down
Loading

0 comments on commit 55c8a73

Please sign in to comment.