mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
Relative date filter JSON format mismatch causes parse failure on /objects/meetings page
https://sonarly.com/issue/24634?type=bug A relative date filter value stored in JSON format (`{"unit":"DAY","amount":7,"direction":"NEXT"}`) cannot be parsed by the regex-based `relativeDateFilterStringifiedSchema` which expects the format `NEXT_7_DAY;;timezone;;`, causing a crash on the /objects/meetings page when resolving view filters into GraphQL operation filters. Fix: Three changes fix both the source of bad data and handle existing bad data: 1. **WorkflowStepFilterValueInput.tsx (line 111)**: Changed `JSON.stringify(newRelativeDateFilter)` to `stringifyRelativeDateFilter(newRelativeDateFilter)`. This ensures new workflow step filter values are serialized in the expected regex-stringified format (`NEXT_7_DAY;;timezone;;`) instead of raw JSON. The import was already present on line 12. 2. **resolveRelativeDateTimeFilterStringified.ts**: Added `safeParseRelativeDateFilterJSONStringified` as a fallback when the regex-based `relativeDateFilterStringifiedSchema.safeParse()` fails. This handles existing JSON-format values already persisted in user view filters without breaking the primary parsing path. 3. **resolveRelativeDateFilterStringified.ts**: Same fallback added to the DATE variant (sibling of the DATE_TIME resolver) for consistency, since it has the same vulnerability. 4. **Test file**: Added two test cases verifying that both resolvers correctly handle JSON-stringified filter values as fallback input. The `safeParseRelativeDateFilterJSONStringified` utility already existed in twenty-shared and uses Zod schema validation, so the fallback is safe and well-validated.
This commit is contained in:
parent
455022f652
commit
233f7a7d17
4 changed files with 47 additions and 17 deletions
|
|
@ -108,7 +108,7 @@ export const WorkflowStepFilterValueInput = ({
|
|||
upsertStepFilterSettings({
|
||||
stepFilterToUpsert: {
|
||||
...stepFilter,
|
||||
value: JSON.stringify(newRelativeDateFilter),
|
||||
value: stringifyRelativeDateFilter(newRelativeDateFilter),
|
||||
},
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -33,6 +33,17 @@ describe('resolveRelativeDateFilterStringified', () => {
|
|||
expect(result).not.toBeNull();
|
||||
expect(result?.direction).toBe('NEXT');
|
||||
});
|
||||
|
||||
it('should resolve a JSON-stringified filter as fallback', () => {
|
||||
const result = resolveRelativeDateFilterStringified(
|
||||
JSON.stringify({ direction: 'PAST', amount: 7, unit: 'DAY' }),
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.direction).toBe('PAST');
|
||||
expect(result?.start).toBeDefined();
|
||||
expect(result?.end).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveRelativeDateTimeFilterStringified', () => {
|
||||
|
|
@ -56,4 +67,15 @@ describe('resolveRelativeDateTimeFilterStringified', () => {
|
|||
expect(result?.start).toBeDefined();
|
||||
expect(result?.end).toBeDefined();
|
||||
});
|
||||
|
||||
it('should resolve a JSON-stringified filter as fallback', () => {
|
||||
const result = resolveRelativeDateTimeFilterStringified(
|
||||
JSON.stringify({ direction: 'NEXT', amount: 7, unit: 'DAY' }),
|
||||
);
|
||||
|
||||
expect(result).not.toBeNull();
|
||||
expect(result?.direction).toBe('NEXT');
|
||||
expect(result?.start).toBeDefined();
|
||||
expect(result?.end).toBeDefined();
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { relativeDateFilterStringifiedSchema } from '@/utils/filter/dates/utils/relativeDateFilterStringifiedSchema';
|
||||
import { resolveRelativeDateFilter } from '@/utils/filter/dates/utils/resolveRelativeDateFilter';
|
||||
import { safeParseRelativeDateFilterJSONStringified } from '@/utils/safeParseRelativeDateFilterJSONStringified';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'class-validator';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
|
@ -16,12 +17,14 @@ export const resolveRelativeDateFilterStringified = (
|
|||
relativeDateFilterStringified,
|
||||
);
|
||||
|
||||
if (!relativeDateFilterParseResult.success) {
|
||||
const relativeDateFilter = relativeDateFilterParseResult.success
|
||||
? relativeDateFilterParseResult.data
|
||||
: safeParseRelativeDateFilterJSONStringified(relativeDateFilterStringified);
|
||||
|
||||
if (!isDefined(relativeDateFilter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const relativeDateFilter = relativeDateFilterParseResult.data;
|
||||
|
||||
const referenceTodayZonedDateTime = isDefined(relativeDateFilter.timezone)
|
||||
? Temporal.Now.zonedDateTimeISO(relativeDateFilter.timezone)
|
||||
: Temporal.Now.zonedDateTimeISO();
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
import { relativeDateFilterStringifiedSchema } from '@/utils/filter/dates/utils/relativeDateFilterStringifiedSchema';
|
||||
import { resolveRelativeDateTimeFilter } from '@/utils/filter/dates/utils/resolveRelativeDateTimeFilter';
|
||||
import { safeParseRelativeDateFilterJSONStringified } from '@/utils/safeParseRelativeDateFilterJSONStringified';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'class-validator';
|
||||
import { Temporal } from 'temporal-polyfill';
|
||||
|
|
@ -16,20 +17,24 @@ export const resolveRelativeDateTimeFilterStringified = (
|
|||
relativeDateTimeFilterStringified,
|
||||
);
|
||||
|
||||
if (relativeDateFilterParseResult.success) {
|
||||
const relativeDateFilter = relativeDateFilterParseResult.data;
|
||||
const relativeDateFilter = relativeDateFilterParseResult.success
|
||||
? relativeDateFilterParseResult.data
|
||||
: safeParseRelativeDateFilterJSONStringified(
|
||||
relativeDateTimeFilterStringified,
|
||||
);
|
||||
|
||||
const referenceTodayZonedDateTime = isDefined(relativeDateFilter.timezone)
|
||||
? Temporal.Now.zonedDateTimeISO(relativeDateFilter.timezone)
|
||||
: Temporal.Now.zonedDateTimeISO();
|
||||
|
||||
const relativeDateFilterWithDateRange = resolveRelativeDateTimeFilter(
|
||||
relativeDateFilter,
|
||||
referenceTodayZonedDateTime.round({ smallestUnit: 'second' }),
|
||||
);
|
||||
|
||||
return relativeDateFilterWithDateRange;
|
||||
} else {
|
||||
if (!isDefined(relativeDateFilter)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const referenceTodayZonedDateTime = isDefined(relativeDateFilter.timezone)
|
||||
? Temporal.Now.zonedDateTimeISO(relativeDateFilter.timezone)
|
||||
: Temporal.Now.zonedDateTimeISO();
|
||||
|
||||
const relativeDateFilterWithDateRange = resolveRelativeDateTimeFilter(
|
||||
relativeDateFilter,
|
||||
referenceTodayZonedDateTime.round({ smallestUnit: 'second' }),
|
||||
);
|
||||
|
||||
return relativeDateFilterWithDateRange;
|
||||
};
|
||||
|
|
|
|||
Loading…
Reference in a new issue