diff --git a/static/app/types/workflowEngine/dataConditions.tsx b/static/app/types/workflowEngine/dataConditions.tsx index 03241953b2ed6c..69fcdf001d1fa1 100644 --- a/static/app/types/workflowEngine/dataConditions.tsx +++ b/static/app/types/workflowEngine/dataConditions.tsx @@ -1,4 +1,8 @@ import {PriorityLevel} from 'sentry/types/group'; +import type { + Attribute, + MatchType, +} from 'sentry/views/automations/components/actionFilters/constants'; import type {Action} from './actions'; @@ -106,3 +110,21 @@ export interface DataConditionHandler { type: DataConditionType; handlerSubgroup?: DataConditionHandlerSubgroupType; } + +interface BaseSubfilter { + id: string; + match: MatchType; + value: string; +} + +export interface AttributeSubfilter extends BaseSubfilter { + attribute: Attribute; + key: never; +} + +export interface TagSubfilter extends BaseSubfilter { + attribute: never; + key: string; +} + +export type Subfilter = AttributeSubfilter | TagSubfilter | BaseSubfilter; diff --git a/static/app/views/automations/components/actionFilters/constants.tsx b/static/app/views/automations/components/actionFilters/constants.tsx index 5899d32acba158..f6e67a1298dd79 100644 --- a/static/app/views/automations/components/actionFilters/constants.tsx +++ b/static/app/views/automations/components/actionFilters/constants.tsx @@ -55,7 +55,7 @@ export enum ModelAge { NEWEST = 'newest', } -export enum Attributes { +export enum Attribute { MESSAGE = 'message', PLATFORM = 'platform', ENVIRONMENT = 'environment', diff --git a/static/app/views/automations/components/actionFilters/eventAttribute.tsx b/static/app/views/automations/components/actionFilters/eventAttribute.tsx index 287c43a08bab1c..66afafcbf6da7a 100644 --- a/static/app/views/automations/components/actionFilters/eventAttribute.tsx +++ b/static/app/views/automations/components/actionFilters/eventAttribute.tsx @@ -4,7 +4,7 @@ import {t, tct} from 'sentry/locale'; import type {SelectValue} from 'sentry/types/core'; import type {DataCondition} from 'sentry/types/workflowEngine/dataConditions'; import { - Attributes, + Attribute, MATCH_CHOICES, type MatchType, } from 'sentry/views/automations/components/actionFilters/constants'; @@ -38,11 +38,11 @@ function AttributeField() { aria-label={t('Attribute')} placeholder={t('attribute')} value={condition.comparison.attribute} - options={Object.values(Attributes).map(attribute => ({ + options={Object.values(Attribute).map(attribute => ({ value: attribute, label: attribute, }))} - onChange={(option: SelectValue) => { + onChange={(option: SelectValue) => { onUpdate({comparison: {...condition.comparison, attribute: option.value}}); }} /> diff --git a/static/app/views/automations/components/actionFilters/subfiltersList.tsx b/static/app/views/automations/components/actionFilters/subfiltersList.tsx index f46f5da75028de..c1861ac9b66cd9 100644 --- a/static/app/views/automations/components/actionFilters/subfiltersList.tsx +++ b/static/app/views/automations/components/actionFilters/subfiltersList.tsx @@ -11,18 +11,31 @@ import {IconAdd, IconDelete} from 'sentry/icons'; import {t, tct} from 'sentry/locale'; import {space} from 'sentry/styles/space'; import type {SelectValue} from 'sentry/types/core'; -import {DataConditionType} from 'sentry/types/workflowEngine/dataConditions'; import { - Attributes, + DataConditionType, + type AttributeSubfilter, + type Subfilter, + type TagSubfilter, +} from 'sentry/types/workflowEngine/dataConditions'; +import { + Attribute, MatchType, } from 'sentry/views/automations/components/actionFilters/constants'; import {useAutomationBuilderErrorContext} from 'sentry/views/automations/components/automationBuilderErrorContext'; import {useDataConditionNodeContext} from 'sentry/views/automations/components/dataConditionNodes'; +function isAttributeSubfilter(subfilter: Subfilter): subfilter is AttributeSubfilter { + return 'attribute' in subfilter; +} + +function isTagSubfilter(subfilter: Subfilter): subfilter is TagSubfilter { + return 'key' in subfilter; +} + interface SubfilterProps { - onUpdate: (comparison: Record) => void; + onUpdate: (comparison: Subfilter) => void; removeError: () => void; - subfilter: Record; + subfilter: Subfilter; subfilter_id: string; } @@ -48,6 +61,8 @@ export function SubfiltersList() { ...subfilters, { id: uuid4(), + match: MatchType.EQUAL, + value: '', }, ]; onUpdate({comparison: {...condition.comparison, filters: newSubfilters}}); @@ -55,13 +70,13 @@ export function SubfiltersList() { function removeSubfilter(id: string) { const newSubfilters = subfilters.filter( - (subfilter: Record) => subfilter.id !== id + (subfilter: Subfilter) => subfilter.id !== id ); onUpdate({comparison: {...condition.comparison, filters: newSubfilters}}); } - function updateSubfilter(id: string, newSubfilter: Record) { - const newSubfilters = subfilters.map((subfilter: Record) => { + function updateSubfilter(id: string, newSubfilter: Subfilter) { + const newSubfilters = subfilters.map((subfilter: Subfilter) => { if (subfilter.id === id) { return newSubfilter; } @@ -73,14 +88,14 @@ export function SubfiltersList() { return (
- {subfilters.map((subfilter: Record, i: number) => { + {subfilters.map((subfilter: Subfilter, i: number) => { return ( updateSubfilter(subfilter.id, newSubfilter), - removeError: () => removeError(subfilter.id), + removeError: () => removeError(condition.id), }} key={subfilter.id} > @@ -148,64 +163,63 @@ function Branch({lastChild}: BranchProps) { function ComparisonTypeField() { const {subfilter, subfilter_id, onUpdate} = useSubfilterContext(); - if (!subfilter.type) { + if (isAttributeSubfilter(subfilter) || isTagSubfilter(subfilter)) { return ( - ) => { - onUpdate({ - id: subfilter.id, - type: option.value, - match: MatchType.EQUAL, - value: '', - ...(option.value === DataConditionType.EVENT_ATTRIBUTE - ? {attribute: Attributes.MESSAGE} - : {}), - }); - }} - /> + + {isAttributeSubfilter(subfilter) ? : } + + + ); } - return ( - - {subfilter.type === DataConditionType.EVENT_ATTRIBUTE ? ( - - ) : ( - - )} - - - + ) => { + onUpdate({ + id: subfilter.id, + match: subfilter.match, + value: subfilter.value, + ...(option.value === DataConditionType.EVENT_ATTRIBUTE + ? {attribute: Attribute.MESSAGE} + : {key: ''}), + } as Subfilter); + }} + /> ); } function AttributeField() { const {subfilter, subfilter_id, onUpdate} = useSubfilterContext(); + + if (!isAttributeSubfilter(subfilter)) { + return null; + } + return ( ({ + value={subfilter.attribute} + options={Object.values(Attribute).map(attribute => ({ value: attribute, label: attribute, }))} - onChange={(option: SelectValue) => { + onChange={(option: SelectValue) => { onUpdate({...subfilter, attribute: option.value}); }} /> @@ -214,12 +228,17 @@ function AttributeField() { function KeyField() { const {subfilter, subfilter_id, onUpdate, removeError} = useSubfilterContext(); + + if (!isTagSubfilter(subfilter)) { + return null; + } + return ( ) => { onUpdate({...subfilter, key: e.target.value}); removeError(); @@ -259,7 +278,7 @@ function ValueField() { name={`${subfilter_id}.value`} aria-label={t('Value')} placeholder={t('value')} - value={`${subfilter.value ?? ''}`} + value={subfilter.value ?? ''} onChange={(e: React.ChangeEvent) => { onUpdate({...subfilter, value: e.target.value}); removeError(); @@ -268,11 +287,7 @@ function ValueField() { ); } -export function SubfilterDetailsList({ - subfilters, -}: { - subfilters: Array>; -}) { +export function SubfilterDetailsList({subfilters}: {subfilters: Subfilter[]}) { return ( {subfilters.map((subfilter, index) => ( @@ -284,20 +299,32 @@ export function SubfilterDetailsList({ ); } -function SubfilterDetails({subfilter}: {subfilter: Record}) { +function SubfilterDetails({subfilter}: {subfilter: Subfilter}) { return tct('[item] [match] [value]', { - item: subfilter.attribute ?? subfilter.key, + item: isAttributeSubfilter(subfilter) + ? subfilter.attribute + : isTagSubfilter(subfilter) + ? subfilter.key + : '', match: subfilter.match === MatchType.EQUAL ? t('is') : t('is not'), - value: subfilter.value, + value: subfilter.value ?? '', }); } -export function validateSubfilters( - subfilters: Array> -): string | undefined { +export function validateSubfilters(subfilters: Subfilter[]): string | undefined { for (const subfilter of subfilters) { - const isMissingAttributeOrTag = !subfilter.attribute && !subfilter.key; - if (isMissingAttributeOrTag || !subfilter.match || !subfilter.value) { + const isMissingAttributeOrTag = + !isAttributeSubfilter(subfilter) && !isTagSubfilter(subfilter); + const missingAttribute = isAttributeSubfilter(subfilter) && !subfilter.attribute; + const missingKey = isTagSubfilter(subfilter) && !subfilter.key; + + if ( + isMissingAttributeOrTag || + missingAttribute || + missingKey || + !subfilter.match || + !subfilter.value + ) { return t('Ensure all subfilters are filled in.'); } } diff --git a/static/app/views/automations/components/automationFormData.tsx b/static/app/views/automations/components/automationFormData.tsx index 4c830b2e9d532c..3445c36894f356 100644 --- a/static/app/views/automations/components/automationFormData.tsx +++ b/static/app/views/automations/components/automationFormData.tsx @@ -23,17 +23,16 @@ 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; - return subfilterWithoutTypeAndId; +const stripSubfilterId = (subfilter: any) => { + const {id: _id, ...subfilterWithoutId} = subfilter; + return subfilterWithoutId; }; const stripActionId = (action: any) => { diff --git a/static/app/views/automations/components/dataConditionNodes.tsx b/static/app/views/automations/components/dataConditionNodes.tsx index f83b021f2a9aec..7ba16480825802 100644 --- a/static/app/views/automations/components/dataConditionNodes.tsx +++ b/static/app/views/automations/components/dataConditionNodes.tsx @@ -20,7 +20,7 @@ import { } from 'sentry/views/automations/components/actionFilters/assignedTo'; import { AgeComparison, - Attributes, + Attribute, Interval, Level, MatchType, @@ -245,7 +245,7 @@ export const dataConditionNodesMap = new Map