Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -148,48 +148,42 @@ function Branch({lastChild}: BranchProps) {
function ComparisonTypeField() {
const {subfilter, subfilter_id, onUpdate} = useSubfilterContext();

if (!subfilter.type) {
if ('attribute' in subfilter || 'key' in subfilter) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It might be helpful to define the types as type Subfilter = AttributeSubfilter | KeySubfilter

And then you can use a user defined type predicate to narrow the type:

function isAttributeSubfilter(subfilter: Subfilter): subfilter is AttributeSubfilter {
  return ''attribute' in subfilter
}

This improves the readability and gives better type safety.

I'm noticing that subfilter is typed as Record<string, any> which we can definitely go back and improve at some point (doesn't have to be in this PR)

return (
<AutomationBuilderSelect
name={`${subfilter_id}.type`}
aria-label={t('Comparison type')}
value={subfilter.type ?? ''}
placeholder={t('Select value type')}
options={[
{
label: t('Attribute'),
value: DataConditionType.EVENT_ATTRIBUTE,
},
{
label: t('Tag'),
value: DataConditionType.TAGGED_EVENT,
},
]}
onChange={(option: SelectValue<DataConditionType>) => {
onUpdate({
id: subfilter.id,
type: option.value,
match: MatchType.EQUAL,
value: '',
...(option.value === DataConditionType.EVENT_ATTRIBUTE
? {attribute: Attributes.MESSAGE}
: {}),
});
}}
/>
<Fragment>
{'attribute' in subfilter ? <AttributeField /> : <KeyField />}
<MatchField />
<ValueField />
</Fragment>
);
}

return (
<Fragment>
{subfilter.type === DataConditionType.EVENT_ATTRIBUTE ? (
<AttributeField />
) : (
<KeyField />
)}
<MatchField />
<ValueField />
</Fragment>
<AutomationBuilderSelect
name={`${subfilter_id}.type`}
aria-label={t('Comparison type')}
value={subfilter.type ?? ''}
placeholder={t('Select value type')}
options={[
{
label: t('Attribute'),
value: DataConditionType.EVENT_ATTRIBUTE,
},
{
label: t('Tag'),
value: DataConditionType.TAGGED_EVENT,
},
]}
onChange={(option: SelectValue<DataConditionType>) => {
onUpdate({
id: subfilter.id,
match: MatchType.EQUAL,
value: '',
...(option.value === DataConditionType.EVENT_ATTRIBUTE
? {attribute: Attributes.MESSAGE}
: {key: undefined}),
});
}}
/>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Subfilter Type Switching Causes Property Conflicts

Switching subfilter types in ComparisonTypeField creates invalid subfilter objects. The onChange handler adds the new property (attribute or key) but doesn't remove the old one, leading to subfilters having both simultaneously. This violates their type definitions and causes type guards like isAttributeSubfilter and isTagSubfilter to return true for the same object. This results in ambiguous UI rendering and incorrect validation.

Additional Locations (1)

Fix in Cursor Fix in Web

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

incorrect, once you select a type you can't change it since the selector disappears

);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,16 +23,15 @@ const stripDataConditionId = (condition: any) => {
...conditionWithoutId,
comparison: {
...condition.comparison,
filters: condition.comparison.filters?.map(stripSubfilterTypeAndId) || [],
filters: condition.comparison.filters?.map(stripSubfilterId) || [],
},
};
}
return conditionWithoutId;
};

// subfilters have a `type` for the frontend to distinguish between attribute and tag comparisons, but this is not expected by the backend
const stripSubfilterTypeAndId = (subfilter: any) => {
const {id: _id, type: _type, ...subfilterWithoutTypeAndId} = subfilter;
const stripSubfilterId = (subfilter: any) => {
const {id: _id, ...subfilterWithoutTypeAndId} = subfilter;
return subfilterWithoutTypeAndId;
};

Expand Down
Loading