feat: 🎸 added higher resoulution options in the dateTime Filter (#16548)
Title: "feat: Add second, minute & hour resolution options to relative date Filter action" --- ## Summary This PR enables support for smaller time units — **Seconds, Minutes, and Hours** — in the *Relative Date* filter used in workflows, rather than being limited to days only. --- ## What Changed This PR extends the relative date filter to include support for the following units: ✔️ `SECOND` ✔️ `MINUTE` ✔️ `HOUR` ✔️ (Existing: `DAY`, `WEEK`, `MONTH`, etc.) Changes include: - Adding `SECOND`, `MINUTE`, and `HOUR` options to the internal relative date unit enum/constant. - Updating utility functions and parsers to correctly interpret and evaluate these new units. - Enhancing existing tests and adding new tests to cover second, minute, and hour relative filters. --- ## Testing New and updated tests include: - Unit tests for serialization of relative filter values including seconds, minutes, and hours. - Workflow filter evaluation tests that verify minute/hour resolution behaves correctly. Tests are included in the changeset. --- ## Backward Compatibility This change is fully backward compatible: - All existing relative date filters using days or larger units behave exactly as before. - Adding finer units does not alter existing stored data or workflow definitions. --- ## Issue Reference Fixes: **twentyhq/twenty#15525** <img width="1909" height="896" alt="image" src="https://github.com/user-attachments/assets/328d03dc-ca0b-4c3f-84e5-58961c178398" /> --------- Co-authored-by: Guillim <guillim@users.noreply.github.com> Co-authored-by: guillim <guigloo@msn.com>
This commit is contained in:
co-authored by
Guillim
guillim
parent
100d4f7f42
commit
a560e8ac83
+346
@@ -1,10 +1,16 @@
|
||||
import {
|
||||
addDays,
|
||||
addHours,
|
||||
addMinutes,
|
||||
addMonths,
|
||||
addSeconds,
|
||||
addWeeks,
|
||||
addYears,
|
||||
subDays,
|
||||
subHours,
|
||||
subMinutes,
|
||||
subMonths,
|
||||
subSeconds,
|
||||
subWeeks,
|
||||
subYears,
|
||||
} from 'date-fns';
|
||||
@@ -104,6 +110,133 @@ describe('Relative Date Filter Utils', () => {
|
||||
|
||||
describe('evaluateRelativeDateFilter', () => {
|
||||
describe('NEXT direction', () => {
|
||||
it('should return true for dates within the next N seconds', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'NEXT',
|
||||
amount: 3,
|
||||
unit: 'SECOND',
|
||||
};
|
||||
|
||||
// Dates within the next 3 seconds should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addSeconds(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addSeconds(now, 2),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addSeconds(now, 3),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Dates outside the range should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addSeconds(now, 4),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subSeconds(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within the next N minutes', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'NEXT',
|
||||
amount: 3,
|
||||
unit: 'MINUTE',
|
||||
};
|
||||
|
||||
// Dates within the next 3 minutes should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addMinutes(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addMinutes(now, 2),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addMinutes(now, 3),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Dates outside the range should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addMinutes(now, 4),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subMinutes(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within the next N hours', () => {
|
||||
///
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'NEXT',
|
||||
amount: 3,
|
||||
unit: 'HOUR',
|
||||
};
|
||||
|
||||
// Dates within the next 3 hours should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addHours(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addHours(now, 2),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addHours(now, 3),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Dates outside the range should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addHours(now, 4),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subHours(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within the next N days', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'NEXT',
|
||||
@@ -270,6 +403,132 @@ describe('Relative Date Filter Utils', () => {
|
||||
});
|
||||
|
||||
describe('PAST direction', () => {
|
||||
it('should return true for dates within the past N seconds', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'PAST',
|
||||
amount: 3,
|
||||
unit: 'SECOND',
|
||||
};
|
||||
|
||||
// Dates within the past 3 seconds should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subSeconds(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subSeconds(now, 2),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subSeconds(now, 3),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Dates outside the range should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subSeconds(now, 4),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addSeconds(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within the past N minutes', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'PAST',
|
||||
amount: 3,
|
||||
unit: 'MINUTE',
|
||||
};
|
||||
|
||||
// Dates within the past 3 minutes should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subMinutes(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subMinutes(now, 2),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subMinutes(now, 3),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Dates outside the range should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subMinutes(now, 4),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addMinutes(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within the past N hours', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'PAST',
|
||||
amount: 3,
|
||||
unit: 'HOUR',
|
||||
};
|
||||
|
||||
// Dates within the past 3 hours should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subHours(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subHours(now, 2),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subHours(now, 3),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Dates outside the range should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subHours(now, 4),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: addHours(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within the past N days', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'PAST',
|
||||
@@ -436,6 +695,93 @@ describe('Relative Date Filter Utils', () => {
|
||||
});
|
||||
|
||||
describe('THIS direction', () => {
|
||||
it('should return true for dates within this second', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'THIS',
|
||||
unit: 'SECOND',
|
||||
};
|
||||
|
||||
// Same second should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: now,
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Different seconds should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: new Date('2024-01-15T08:00:00Z'),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subSeconds(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within this minute', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'THIS',
|
||||
unit: 'MINUTE',
|
||||
};
|
||||
|
||||
// Same minute should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: now,
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Different minutes should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: new Date('2024-01-15T08:00:00Z'),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subMinutes(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within this hour', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'THIS',
|
||||
unit: 'HOUR',
|
||||
};
|
||||
|
||||
// Same hour should match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: now,
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(true);
|
||||
|
||||
// Different hours should not match
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: new Date('2024-01-15T08:00:00Z'),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateRelativeDateFilter({
|
||||
dateToCheck: subHours(now, 1),
|
||||
relativeDateFilterValue,
|
||||
}),
|
||||
).toBe(false);
|
||||
});
|
||||
|
||||
it('should return true for dates within this day', () => {
|
||||
const relativeDateFilterValue: RelativeDateFilter = {
|
||||
direction: 'THIS',
|
||||
|
||||
+21
@@ -1,12 +1,18 @@
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import {
|
||||
endOfDay,
|
||||
endOfHour,
|
||||
endOfSecond,
|
||||
endOfMinute,
|
||||
endOfMonth,
|
||||
endOfWeek,
|
||||
endOfYear,
|
||||
isWithinInterval,
|
||||
startOfDay,
|
||||
startOfHour,
|
||||
startOfMinute,
|
||||
startOfMonth,
|
||||
startOfSecond,
|
||||
startOfWeek,
|
||||
startOfYear,
|
||||
} from 'date-fns';
|
||||
@@ -122,6 +128,21 @@ function evaluateThisDirection(
|
||||
: 1;
|
||||
|
||||
switch (unit) {
|
||||
case 'SECOND':
|
||||
return isWithinInterval(dateToCheck, {
|
||||
start: startOfSecond(now),
|
||||
end: endOfSecond(now),
|
||||
});
|
||||
case 'MINUTE':
|
||||
return isWithinInterval(dateToCheck, {
|
||||
start: startOfMinute(now),
|
||||
end: endOfMinute(now),
|
||||
});
|
||||
case 'HOUR':
|
||||
return isWithinInterval(dateToCheck, {
|
||||
start: startOfHour(now),
|
||||
end: endOfHour(now),
|
||||
});
|
||||
case 'DAY':
|
||||
return isWithinInterval(dateToCheck, {
|
||||
start: startOfDay(now),
|
||||
|
||||
Reference in New Issue
Block a user