How I contribute to Sentry's Alert Feature
Identifying the Issue.
Issue #115268 https://github.com/getsentry/sentry/issues/115268
The issue is about a bug on Alert feature, where a user can't create an Alert when it selects 'Tagged Event', even though they have filled all of the required fields. The validation 'Ensure all fields are filled in' will always appear as a blocker.
Diving into the source code
I tried to search which function causes this.
Step 1 - Read the error message location.
export function validateTaggedEventCondition({
condition,
}: ValidateDataConditionProps): string | undefined {
if (!condition.comparison.key || !condition.comparison.match) {
return t('Ensure all fields are filled in.');
}
if (matchRequiresValue(condition.comparison.match) && !condition.comparison.value) {
return t('Ensure all fields are filled in.');
}
return undefined;
}
The validation 'Ensure all fields are filled in' always appear, so either !condition.comparison.key or !condition.comparison.match is undefined or null. It seems the key was never actually saved into state, even though the user typed something.
When picking Tagged Event, we will be asked to input a key, an operator, and a value. Even though the key has been selected automatically, which is Tag, whatever value we typed, is not identified. So the validation always appear.
Step 2 - Trace where key gets set
return (
<AutomationBuilderSelect
disabled={isLoading}
creatable
name={`${condition_id}.comparison.key`}
aria-label={t('Tag')}
placeholder={isLoading ? t('Loading tags\u2026') : t('tag')}
value={condition.comparison.key}
options={sortedOptions}
onChange={(e: SelectValue<MatchType>) => {
onUpdate({comparison: {...condition.comparison, key: e.value}});
removeError(condition.id);
}}
/>
);
}
This onChange is set on KeyField's component. When does onChange is fired?
When we look more into this code, we can see that it uses creatable property. It means the user can type in a customized value that isn't in the dropdown list. In react-select, the Creatable component only fires onChange in two cases:
User presses Enter to confirm the typed value
User clicks a suggestion from the dropdown
So even though React has already filled automatically Tag in the key field, once the user clicks the next field (either Match or Value), which will become blur -> onChange is never fired -> condition.comparison.key is still undefined.
Step 3 - Add a solution
My solution to this problem would be to hook onBlur and manually commit whatever was typed - which will required useRef & onInputChange hook.
I use onInputChange to track every keystroke while typing.
I use useRef to interact with the value that is ref is listening to.
I use onBlur to set what happens when a user clicks away or when the field loses focus.
I use onChange to track when a value is explicitly selected/committed.
return (
<AutomationBuilderSelect
disabled={isLoading}
creatable
name={`${condition_id}.comparison.key`}
aria-label={t('Tag')}
placeholder={isLoading ? t('Loading tags\u2026') : t('tag')}
value={condition.comparison.key}
options={sortedOptions}
onInputChange={(value: string, {action}: {action: string}) => {
// 'menu-close' and 'input-blur' fire with '' right before onBlur and would
// wipe the tracked value, so only track real typing actions
if (action === 'input-change') {
typedValueRef.current = value;
}
}}
onBlur={() => {
if (typedValueRef.current) {
onUpdate({comparison: {...condition.comparison, key: typedValueRef.current}});
removeError(condition.id);
}
}}
onChange={(e: SelectValue<MatchType>) => {
typedValueRef.current = '';
onUpdate({comparison: {...condition.comparison, key: e.value}});
removeError(condition.id);
typedValueRef.current = '';
}}
/>
);
}
I then also change the validation logic:
export function validateTaggedEventCondition({
condition,
}: ValidateDataConditionProps): string | undefined {
if (!condition.comparison.key) {
return t('Select a tag.');
}
if (!condition.comparison.match) {
return t('Select a match type.');
}
if (matchRequiresValue(condition.comparison.match) && !condition.comparison.value) {
return t('Enter a value.');
}
return undefined;
}
Create The Pull Request
Finally, after pushing the commit to remote branch, I create the PR : https://github.com/getsentry/sentry/pull/116120

