fix(workflow): stop relative date filter from crashing on empty/invalid date values (#22384)
## Problem
A workflow **Filter** step on a `DATE`/`DATE_TIME` field using
`IS_RELATIVE` crashes with a `RangeError` when the referenced step
output is empty or invalid. The empty value is coerced into an `Invalid
Date` (`new Date("undefined")`),
whose `.getTime()` is `NaN`, and
`Temporal.Instant.fromEpochMilliseconds(NaN)` throws, failing the
affected workflow runs.
This is a latent regression from the Date → Temporal migration (#16544):
the previous `date-fns` implementation silently returned `false` on an
invalid date, but Temporal is strict and throws. The guard was never
carried over.
## Fix
Validate the coerced date once at the boundary in `evaluateDateFilter` —
the single place arbitrary/empty step output is turned into a `Date`. An
unparseable date now resolves to "does not match" for every comparison
operand (`IS`, `IS_IN_PAST`, `IS_IN_FUTURE`, `IS_TODAY`, `IS_BEFORE`,
`IS_AFTER`, `IS_RELATIVE`), restoring the pre-migration contract.
`IS_EMPTY` / `IS_NOT_EMPTY` are intentionally excluded so emptiness is
still evaluated on the raw operand.
## Tests
Added a parameterized regression test covering every date comparison
operand with empty and missing step output, asserting no throw and a
`false` result.
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22384?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
+39
@@ -1042,6 +1042,45 @@ describe('evaluateFilterConditions', () => {
|
||||
true,
|
||||
);
|
||||
});
|
||||
|
||||
it.each([
|
||||
ViewFilterOperand.IS,
|
||||
ViewFilterOperand.IS_IN_PAST,
|
||||
ViewFilterOperand.IS_IN_FUTURE,
|
||||
ViewFilterOperand.IS_TODAY,
|
||||
ViewFilterOperand.IS_BEFORE,
|
||||
ViewFilterOperand.IS_AFTER,
|
||||
ViewFilterOperand.IS_RELATIVE,
|
||||
])(
|
||||
'should not match and not throw when the left operand is an empty/invalid date (%s)',
|
||||
(operand) => {
|
||||
const relativeValue = JSON.stringify({
|
||||
direction: 'PAST',
|
||||
amount: 1,
|
||||
unit: 'DAY',
|
||||
});
|
||||
|
||||
const emptyStepOutputFilter = createFilter(
|
||||
operand,
|
||||
'',
|
||||
relativeValue,
|
||||
'DATE',
|
||||
);
|
||||
const missingStepOutputFilter = createFilter(
|
||||
operand,
|
||||
undefined,
|
||||
relativeValue,
|
||||
'DATE_TIME',
|
||||
);
|
||||
|
||||
expect(
|
||||
evaluateFilterConditions({ filters: [emptyStepOutputFilter] }),
|
||||
).toBe(false);
|
||||
expect(
|
||||
evaluateFilterConditions({ filters: [missingStepOutputFilter] }),
|
||||
).toBe(false);
|
||||
},
|
||||
);
|
||||
});
|
||||
|
||||
describe('currency operands', () => {
|
||||
|
||||
+12
-6
@@ -216,8 +216,20 @@ function evaluateBooleanFilter(filter: ResolvedFilter): boolean {
|
||||
|
||||
function evaluateDateFilter(filter: ResolvedFilter): boolean {
|
||||
// TODO: refactor this with Temporal
|
||||
if (filter.operand === ViewFilterOperand.IS_EMPTY) {
|
||||
return !isDefined(filter.leftOperand) || filter.leftOperand === '';
|
||||
}
|
||||
|
||||
if (filter.operand === ViewFilterOperand.IS_NOT_EMPTY) {
|
||||
return isDefined(filter.leftOperand) && filter.leftOperand !== '';
|
||||
}
|
||||
|
||||
const dateLeftValue = new Date(String(filter.leftOperand));
|
||||
|
||||
if (Number.isNaN(dateLeftValue.getTime())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (filter.operand) {
|
||||
case ViewFilterOperand.IS:
|
||||
return (
|
||||
@@ -245,12 +257,6 @@ function evaluateDateFilter(filter: ResolvedFilter): boolean {
|
||||
new Date(String(filter.rightOperand)).getTime()
|
||||
);
|
||||
|
||||
case ViewFilterOperand.IS_EMPTY:
|
||||
return !isDefined(filter.leftOperand) || filter.leftOperand === '';
|
||||
|
||||
case ViewFilterOperand.IS_NOT_EMPTY:
|
||||
return isDefined(filter.leftOperand) && filter.leftOperand !== '';
|
||||
|
||||
case ViewFilterOperand.IS_RELATIVE:
|
||||
return parseAndEvaluateRelativeDateFilter({
|
||||
dateToCheck: dateLeftValue,
|
||||
|
||||
Reference in New Issue
Block a user