diff --git a/ts/test-node/types/NotificationProfile_test.ts b/ts/test-node/types/NotificationProfile_test.ts index 07d44f68c2..c980b68f6f 100644 --- a/ts/test-node/types/NotificationProfile_test.ts +++ b/ts/test-node/types/NotificationProfile_test.ts @@ -3,7 +3,7 @@ import { assert } from 'chai'; -import { DAY, HOUR } from '../../util/durations'; +import { DAY, HOUR, MINUTE } from '../../util/durations'; import { DayOfWeek, @@ -382,7 +382,77 @@ describe('NotificationProfile', () => { }); assert.deepEqual(expected, actual); }); - it('should return willDisable with earlier start if two profiles should be active right now', () => { + + it('should return willDisable if profile should be active right now, with earlier preempt time', () => { + const defaultProfile = createBasicProfile({ + createdAtMs: now, + scheduleEnabled: true, + scheduleDaysEnabled: { + [DayOfWeek.MONDAY]: true, + [DayOfWeek.TUESDAY]: false, + [DayOfWeek.WEDNESDAY]: false, + [DayOfWeek.THURSDAY]: false, + [DayOfWeek.FRIDAY]: false, + [DayOfWeek.SATURDAY]: false, + [DayOfWeek.SUNDAY]: false, + }, + scheduleStartTime: 900, + scheduleEndTime: 1200, + }); + const preemptProfile = createBasicProfile({ + createdAtMs: now + 10, + scheduleEnabled: true, + scheduleDaysEnabled: { + [DayOfWeek.MONDAY]: true, + [DayOfWeek.TUESDAY]: false, + [DayOfWeek.WEDNESDAY]: false, + [DayOfWeek.THURSDAY]: false, + [DayOfWeek.FRIDAY]: false, + [DayOfWeek.SATURDAY]: false, + [DayOfWeek.SUNDAY]: false, + }, + scheduleStartTime: 1030, + scheduleEndTime: 1200, + }); + const noPreemptProfile = createBasicProfile({ + createdAtMs: now - 10, + scheduleEnabled: true, + scheduleDaysEnabled: { + [DayOfWeek.MONDAY]: true, + [DayOfWeek.TUESDAY]: false, + [DayOfWeek.WEDNESDAY]: false, + [DayOfWeek.THURSDAY]: false, + [DayOfWeek.FRIDAY]: false, + [DayOfWeek.SATURDAY]: false, + [DayOfWeek.SUNDAY]: false, + }, + scheduleStartTime: 1015, + scheduleEndTime: 1200, + }); + + const expected: NextProfileEvent = { + type: 'willDisable', + activeProfile: defaultProfile.id, + willDisableAt: now + 30 * MINUTE, + }; + const profiles: ReadonlyArray = sortProfiles([ + preemptProfile, + noPreemptProfile, + defaultProfile, + createBasicProfile({ + name: 'Work', + }), + ]); + + const actual = findNextProfileEvent({ + override: undefined, + profiles, + time: now, + }); + assert.deepEqual(expected, actual); + }); + + it('should return willDisable with newer profile if two profiles should be active right now, different start time', () => { const oldProfile = createBasicProfile({ createdAtMs: now - 10, scheduleEnabled: true, @@ -416,7 +486,7 @@ describe('NotificationProfile', () => { const expected: NextProfileEvent = { type: 'willDisable', - activeProfile: oldProfile.id, + activeProfile: newProfile.id, willDisableAt: now + 2 * HOUR, }; const profiles: ReadonlyArray = sortProfiles([ @@ -431,7 +501,7 @@ describe('NotificationProfile', () => { }); assert.deepEqual(expected, actual); }); - it('should return willDisable with newer profile with same start if two profiles should be active right now', () => { + it('should return willDisable with newer profile if two profiles should be active right now, same start time', () => { const oldProfile = createBasicProfile({ createdAtMs: now - 10, scheduleEnabled: true, diff --git a/ts/types/NotificationProfile.ts b/ts/types/NotificationProfile.ts index d9d1feffac..68119a2068 100644 --- a/ts/types/NotificationProfile.ts +++ b/ts/types/NotificationProfile.ts @@ -214,6 +214,16 @@ export function findNextProfileEvent({ profile: activeProfileBySchedule, time, }); + + // A newer profile will preempt this active profile if its schedule overlaps. + const newerProfiles = profiles.filter( + item => item.createdAtMs > activeProfileBySchedule.createdAtMs + ); + const preemptResult = findNextScheduledEnableForAll({ + profiles: newerProfiles, + time: time + 1, + }); + strictAssert( disabledAt, `Schedule ${activeProfileBySchedule.id} is enabled by schedule right now, it should disable soon!` @@ -221,7 +231,9 @@ export function findNextProfileEvent({ return { type: 'willDisable', activeProfile: activeProfileBySchedule.id, - willDisableAt: disabledAt, + willDisableAt: preemptResult + ? Math.min(preemptResult.time, disabledAt) + : disabledAt, }; } @@ -284,24 +296,15 @@ export function areAnyProfilesEnabledBySchedule({ time: number; profiles: ReadonlyArray; }): NotificationProfileType | undefined { - const candidates: Array = []; - + // We find the first match, assuming the array is sorted, newest to oldest for (const profile of profiles) { const result = isProfileEnabledBySchedule({ time, profile }); if (result) { - candidates.push(profile); + return profile; } } - // In the case of conflicts, we want the one that started earlier. Or the one that was - // created more recently in the case of a tie. - const sortedCandidates = orderBy( - candidates, - ['scheduleStartTime', 'createdAtMs'], - ['asc', 'desc'] - ); - - return sortedCandidates[0]; + return undefined; } // Find the next time this profile's schedule will tell it to disable