feat: add "olderThan" segment filter operator

- Added `olderThan` and `triggeredOlderThan` operators to types and schemas
- Implemented backend logic for `olderThan` operator in SegmentService
- Updated SegmentFilterBuilder and shared index/types files to support new time-based operators
This commit is contained in:
Shuvadipta Das
2026-01-12 11:10:53 +05:30
parent 18e17cf198
commit b5fa21a57c
4 changed files with 116 additions and 6 deletions
@@ -28,11 +28,13 @@ const STANDARD_OPERATORS: {value: SegmentFilterOperator; label: string}[] = [
{value: 'exists', label: 'Exists'},
{value: 'notExists', label: 'Does not exist'},
{value: 'within', label: 'Within (time)'},
{value: 'olderThan', label: 'Older than (time)'},
];
const EVENT_OPERATORS: {value: SegmentFilterOperator; label: string}[] = [
{value: 'triggered', label: 'Ever occurred'},
{value: 'triggeredWithin', label: 'Occurred within'},
{value: 'triggeredOlderThan', label: 'Occurred over (time) ago'},
{value: 'notTriggered', label: 'Never occurred'},
];
@@ -172,6 +174,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
'exists',
'notExists',
'within',
'olderThan',
].includes(op.value),
);
}
@@ -183,7 +186,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
}, []);
const needsValue = !['exists', 'notExists', 'triggered', 'notTriggered'].includes(filter.operator);
const needsUnit = ['within', 'triggeredWithin'].includes(filter.operator);
const needsUnit = ['within', 'triggeredWithin', 'olderThan', 'triggeredOlderThan'].includes(filter.operator);
// Get field type from available fields
const fieldOption = useMemo(
@@ -217,6 +220,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
'exists',
'notExists',
'within',
'olderThan',
].includes(op.value),
);
}
@@ -232,7 +236,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
const selectedField = availableFields.find(f => f.value === value);
const newFieldType = selectedField?.type || 'string';
const isEvent = newFieldType === 'event' || newFieldType === 'email';
const currentOperatorIsEvent = ['triggered', 'triggeredWithin', 'notTriggered'].includes(filter.operator);
const currentOperatorIsEvent = ['triggered', 'triggeredWithin', 'triggeredOlderThan', 'notTriggered'].includes(filter.operator);
// Determine default operator and value based on new field type
let newOperator = filter.operator;
@@ -267,7 +271,7 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
newUnit = undefined;
// If the new operator doesn't support units but we had them, ensure value is appropriate
const newOperatorNeedsUnit = ['within', 'triggeredWithin'].includes(newOperator);
const newOperatorNeedsUnit = ['within', 'triggeredWithin', 'olderThan', 'triggeredOlderThan'].includes(newOperator);
if (!newOperatorNeedsUnit) {
// Convert numeric value back to appropriate type for the field
newValue = getDefaultValueForType(newFieldType);
@@ -399,8 +403,8 @@ const FilterRow = memo(function FilterRow({filter, onChange, onRemove, available
// Check if we're switching between operators that need different value types
const oldNeedsValue = !['exists', 'notExists', 'triggered', 'notTriggered'].includes(oldOperator);
const newNeedsValue = !['exists', 'notExists', 'triggered', 'notTriggered'].includes(newOperator);
const oldNeedsUnit = ['within', 'triggeredWithin'].includes(oldOperator);
const newNeedsUnit = ['within', 'triggeredWithin'].includes(newOperator);
const oldNeedsUnit = ['within', 'triggeredWithin', 'olderThan', 'triggeredOlderThan'].includes(oldOperator);
const newNeedsUnit = ['within', 'triggeredWithin', 'olderThan', 'triggeredOlderThan'].includes(newOperator);
const updatedFilter: SegmentFilter = {...filter, operator: newOperator};