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:
@@ -3,6 +3,54 @@ import { safeParseRelativeDateFilterJSONStringified } from '@/utils/safeParseRel
|
||||
describe('safeParseRelativeDateFilterJSONStringified', () => {
|
||||
describe('valid inputs', () => {
|
||||
describe('NEXT direction', () => {
|
||||
it('should parse NEXT direction with SECOND unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'NEXT',
|
||||
amount: 30,
|
||||
unit: 'SECOND',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'NEXT',
|
||||
amount: 30,
|
||||
unit: 'SECOND',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse NEXT direction with MINUTE unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'NEXT',
|
||||
amount: 15,
|
||||
unit: 'MINUTE',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'NEXT',
|
||||
amount: 15,
|
||||
unit: 'MINUTE',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse NEXT direction with HOUR unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'NEXT',
|
||||
amount: 6,
|
||||
unit: 'HOUR',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'NEXT',
|
||||
amount: 6,
|
||||
unit: 'HOUR',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse NEXT direction with DAY unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'NEXT',
|
||||
@@ -69,6 +117,54 @@ describe('safeParseRelativeDateFilterJSONStringified', () => {
|
||||
});
|
||||
|
||||
describe('PAST direction', () => {
|
||||
it('should parse PAST direction with SECOND unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'PAST',
|
||||
amount: 45,
|
||||
unit: 'SECOND',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'PAST',
|
||||
amount: 45,
|
||||
unit: 'SECOND',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse PAST direction with MINUTE unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'PAST',
|
||||
amount: 20,
|
||||
unit: 'MINUTE',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'PAST',
|
||||
amount: 20,
|
||||
unit: 'MINUTE',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse PAST direction with HOUR unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'PAST',
|
||||
amount: 5,
|
||||
unit: 'HOUR',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'PAST',
|
||||
amount: 5,
|
||||
unit: 'HOUR',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse PAST direction with DAY unit', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'PAST',
|
||||
@@ -135,6 +231,48 @@ describe('safeParseRelativeDateFilterJSONStringified', () => {
|
||||
});
|
||||
|
||||
describe('THIS direction', () => {
|
||||
it('should parse THIS direction with SECOND unit (no amount)', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'THIS',
|
||||
unit: 'SECOND',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'THIS',
|
||||
unit: 'SECOND',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse THIS direction with MINUTE unit (no amount)', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'THIS',
|
||||
unit: 'MINUTE',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'THIS',
|
||||
unit: 'MINUTE',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse THIS direction with HOUR unit (no amount)', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'THIS',
|
||||
unit: 'HOUR',
|
||||
});
|
||||
|
||||
const result = safeParseRelativeDateFilterJSONStringified(input);
|
||||
|
||||
expect(result).toEqual({
|
||||
direction: 'THIS',
|
||||
unit: 'HOUR',
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse THIS direction with DAY unit (no amount)', () => {
|
||||
const input = JSON.stringify({
|
||||
direction: 'THIS',
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { type RelativeDateFilterUnit } from '@/utils/filter/dates/utils/relativeDateFilterUnitSchema';
|
||||
import { addDays, addMonths, addWeeks, addYears } from 'date-fns';
|
||||
import {
|
||||
addDays,
|
||||
addHours,
|
||||
addMinutes,
|
||||
addMonths,
|
||||
addSeconds,
|
||||
addWeeks,
|
||||
addYears,
|
||||
} from 'date-fns';
|
||||
|
||||
export const addUnitToDateTime = (
|
||||
dateTime: Date,
|
||||
@@ -7,6 +15,12 @@ export const addUnitToDateTime = (
|
||||
unit: RelativeDateFilterUnit,
|
||||
) => {
|
||||
switch (unit) {
|
||||
case 'SECOND':
|
||||
return addSeconds(dateTime, amount);
|
||||
case 'MINUTE':
|
||||
return addMinutes(dateTime, amount);
|
||||
case 'HOUR':
|
||||
return addHours(dateTime, amount);
|
||||
case 'DAY':
|
||||
return addDays(dateTime, amount);
|
||||
case 'WEEK':
|
||||
|
||||
@@ -3,7 +3,15 @@ import { type FirstDayOfTheWeek } from '@/utils/filter/dates/utils/firstDayOfWee
|
||||
import { getFirstDayOfTheWeekAsANumberForDateFNS } from '@/utils/filter/dates/utils/getFirstDayOfTheWeekAsANumberForDateFNS';
|
||||
import { type RelativeDateFilterUnit } from '@/utils/filter/dates/utils/relativeDateFilterUnitSchema';
|
||||
import { isDefined } from '@/utils/validation';
|
||||
import { endOfDay, endOfMonth, endOfWeek, endOfYear } from 'date-fns';
|
||||
import {
|
||||
endOfDay,
|
||||
endOfHour,
|
||||
endOfMinute,
|
||||
endOfMonth,
|
||||
endOfSecond,
|
||||
endOfWeek,
|
||||
endOfYear,
|
||||
} from 'date-fns';
|
||||
|
||||
export const getEndUnitOfDateTime = (
|
||||
dateTime: Date,
|
||||
@@ -11,6 +19,12 @@ export const getEndUnitOfDateTime = (
|
||||
firstDayOfTheWeek?: Nullable<FirstDayOfTheWeek>,
|
||||
) => {
|
||||
switch (unit) {
|
||||
case 'SECOND':
|
||||
return endOfSecond(dateTime);
|
||||
case 'MINUTE':
|
||||
return endOfMinute(dateTime);
|
||||
case 'HOUR':
|
||||
return endOfHour(dateTime);
|
||||
case 'DAY':
|
||||
return endOfDay(dateTime);
|
||||
case 'WEEK': {
|
||||
|
||||
@@ -3,7 +3,15 @@ import { type FirstDayOfTheWeek } from '@/utils/filter/dates/utils/firstDayOfWee
|
||||
import { getFirstDayOfTheWeekAsANumberForDateFNS } from '@/utils/filter/dates/utils/getFirstDayOfTheWeekAsANumberForDateFNS';
|
||||
import { type RelativeDateFilterUnit } from '@/utils/filter/dates/utils/relativeDateFilterUnitSchema';
|
||||
import { isDefined } from '@/utils/validation';
|
||||
import { startOfDay, startOfMonth, startOfWeek, startOfYear } from 'date-fns';
|
||||
import {
|
||||
startOfDay,
|
||||
startOfHour,
|
||||
startOfMinute,
|
||||
startOfMonth,
|
||||
startOfSecond,
|
||||
startOfWeek,
|
||||
startOfYear,
|
||||
} from 'date-fns';
|
||||
|
||||
export const getStartUnitOfDateTime = (
|
||||
dateTime: Date,
|
||||
@@ -11,6 +19,12 @@ export const getStartUnitOfDateTime = (
|
||||
firstDayOfTheWeek?: Nullable<FirstDayOfTheWeek>,
|
||||
) => {
|
||||
switch (unit) {
|
||||
case 'SECOND':
|
||||
return startOfSecond(dateTime);
|
||||
case 'MINUTE':
|
||||
return startOfMinute(dateTime);
|
||||
case 'HOUR':
|
||||
return startOfHour(dateTime);
|
||||
case 'DAY':
|
||||
return startOfDay(dateTime);
|
||||
case 'WEEK': {
|
||||
|
||||
+1
-1
@@ -3,7 +3,7 @@ import { isNonEmptyArray } from '@sniptt/guards';
|
||||
import z from 'zod';
|
||||
|
||||
const REGEX_FOR_RELATIVE_DATE_FILTER_STRINGIFIED_PARSING =
|
||||
/((?:THIS)|(?:PAST)|(?:NEXT))_(\d*)_(DAY|MONTH|YEAR|WEEK)(?:(?:;;([^;;]*);;)?(?:(MONDAY|SUNDAY|SATURDAY);;)?)?/;
|
||||
/((?:THIS)|(?:PAST)|(?:NEXT))_(\d*)_(DAY|MONTH|YEAR|WEEK|HOUR|MINUTE|SECOND)(?:(?:;;([^;;]*);;)?(?:(MONDAY|SUNDAY|SATURDAY);;)?)?/;
|
||||
|
||||
export const relativeDateFilterStringifiedSchema = z
|
||||
.string()
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import z from 'zod';
|
||||
|
||||
export const relativeDateFilterUnitSchema = z.enum([
|
||||
'SECOND',
|
||||
'MINUTE',
|
||||
'HOUR',
|
||||
'DAY',
|
||||
'WEEK',
|
||||
'MONTH',
|
||||
|
||||
@@ -1,5 +1,13 @@
|
||||
import { type RelativeDateFilterUnit } from '@/utils/filter/dates/utils/relativeDateFilterUnitSchema';
|
||||
import { subDays, subMonths, subWeeks, subYears } from 'date-fns';
|
||||
import {
|
||||
subDays,
|
||||
subHours,
|
||||
subMinutes,
|
||||
subMonths,
|
||||
subSeconds,
|
||||
subWeeks,
|
||||
subYears,
|
||||
} from 'date-fns';
|
||||
|
||||
export const subUnitFromDateTime = (
|
||||
dateTime: Date,
|
||||
@@ -7,6 +15,12 @@ export const subUnitFromDateTime = (
|
||||
unit: RelativeDateFilterUnit,
|
||||
) => {
|
||||
switch (unit) {
|
||||
case 'SECOND':
|
||||
return subSeconds(dateTime, amount);
|
||||
case 'MINUTE':
|
||||
return subMinutes(dateTime, amount);
|
||||
case 'HOUR':
|
||||
return subHours(dateTime, amount);
|
||||
case 'DAY':
|
||||
return subDays(dateTime, amount);
|
||||
case 'WEEK':
|
||||
|
||||
Reference in New Issue
Block a user