diff --git a/static/app/types/workflowEngine/dataConditions.tsx b/static/app/types/workflowEngine/dataConditions.tsx
index 6bfa2cf9422cb5..6f0fb1b4930b03 100644
--- a/static/app/types/workflowEngine/dataConditions.tsx
+++ b/static/app/types/workflowEngine/dataConditions.tsx
@@ -24,6 +24,7 @@ export enum DataConditionType {
EXISTING_HIGH_PRIORITY_ISSUE = 'existing_high_priority_issue',
FIRST_SEEN_EVENT = 'first_seen_event',
ISSUE_CATEGORY = 'issue_category',
+ ISSUE_TYPE = 'issue_type',
ISSUE_OCCURRENCES = 'issue_occurrences',
LATEST_ADOPTED_RELEASE = 'latest_adopted_release',
LATEST_RELEASE = 'latest_release',
diff --git a/static/app/views/automations/components/actionFilters/issueType.tsx b/static/app/views/automations/components/actionFilters/issueType.tsx
new file mode 100644
index 00000000000000..f85fa0fbb41452
--- /dev/null
+++ b/static/app/views/automations/components/actionFilters/issueType.tsx
@@ -0,0 +1,88 @@
+import {AutomationBuilderSelect} from 'sentry/components/workflowEngine/form/automationBuilderSelect';
+import {t, tct} from 'sentry/locale';
+import type {SelectValue} from 'sentry/types/core';
+import {getIssueTitleFromType, VISIBLE_ISSUE_TYPES} from 'sentry/types/group';
+import type {DataCondition} from 'sentry/types/workflowEngine/dataConditions';
+import {useAutomationBuilderErrorContext} from 'sentry/views/automations/components/automationBuilderErrorContext';
+import type {ValidateDataConditionProps} from 'sentry/views/automations/components/automationFormData';
+import {useDataConditionNodeContext} from 'sentry/views/automations/components/dataConditionNodes';
+
+const INCLUDE_CHOICES = [
+ {value: true, label: t('equal to')},
+ {value: false, label: t('not equal to')},
+];
+
+type IssueTypeChoice = {
+ label: string;
+ value: string;
+};
+
+const ISSUE_TYPE_CHOICES: IssueTypeChoice[] = VISIBLE_ISSUE_TYPES.map(value => ({
+ value,
+ label: getIssueTitleFromType(value) ?? value,
+}));
+
+export function IssueTypeDetails({condition}: {condition: DataCondition}) {
+ const include = condition.comparison.include ?? true;
+ const includeLabel =
+ INCLUDE_CHOICES.find(choice => choice.value === include)?.label ?? '';
+
+ return tct('Issue type is [include] [type]', {
+ include: includeLabel,
+ type:
+ ISSUE_TYPE_CHOICES.find(choice => choice.value === condition.comparison.value)
+ ?.label || condition.comparison.value,
+ });
+}
+
+export function IssueTypeNode() {
+ return tct('Issue type is [include] [type]', {
+ include: ,
+ type: ,
+ });
+}
+
+function IncludeField() {
+ const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
+ const {removeError} = useAutomationBuilderErrorContext();
+
+ return (
+ ) => {
+ onUpdate({comparison: {...condition.comparison, include: option.value}});
+ removeError(condition.id);
+ }}
+ />
+ );
+}
+
+function TypeField() {
+ const {condition, condition_id, onUpdate} = useDataConditionNodeContext();
+ const {removeError} = useAutomationBuilderErrorContext();
+
+ return (
+ ) => {
+ onUpdate({comparison: {...condition.comparison, value: option.value}});
+ removeError(condition.id);
+ }}
+ />
+ );
+}
+
+export function validateIssueTypeCondition({
+ condition,
+}: ValidateDataConditionProps): string | undefined {
+ if (condition.comparison.value === undefined || condition.comparison.value === null) {
+ return t('You must select an issue type.');
+ }
+ return undefined;
+}
diff --git a/static/app/views/automations/components/dataConditionNodeList.spec.tsx b/static/app/views/automations/components/dataConditionNodeList.spec.tsx
index e4e43a6998b4ab..ff690bacdf1585 100644
--- a/static/app/views/automations/components/dataConditionNodeList.spec.tsx
+++ b/static/app/views/automations/components/dataConditionNodeList.spec.tsx
@@ -4,6 +4,7 @@ import {DataConditionHandlerFixture} from 'sentry-fixture/workflowEngine';
import {render, screen, userEvent, within} from 'sentry-test/reactTestingLibrary';
+import {IssueType} from 'sentry/types/group';
import type {DataConditionHandler} from 'sentry/types/workflowEngine/dataConditions';
import {
DataConditionGroupLogicType,
@@ -25,6 +26,9 @@ const dataConditionHandlers: DataConditionHandler[] = [
DataConditionHandlerFixture({
type: DataConditionType.ISSUE_PRIORITY_DEESCALATING,
}),
+ DataConditionHandlerFixture({
+ type: DataConditionType.ISSUE_TYPE,
+ }),
DataConditionHandlerFixture({
type: DataConditionType.EVENT_FREQUENCY,
handlerSubgroup: DataConditionHandlerSubgroupType.EVENT_ATTRIBUTES,
@@ -115,11 +119,12 @@ describe('DataConditionNodeList', () => {
);
await userEvent.click(screen.getByRole('textbox', {name: 'Add condition'}));
- expect(screen.getAllByRole('menuitemradio')).toHaveLength(4);
+ expect(screen.getAllByRole('menuitemradio')).toHaveLength(5);
expect(screen.getByRole('menuitemradio', {name: 'Issue age'})).toBeInTheDocument();
expect(
screen.getByRole('menuitemradio', {name: 'Issue priority'})
).toBeInTheDocument();
+ expect(screen.getByRole('menuitemradio', {name: 'Issue type'})).toBeInTheDocument();
expect(
screen.getByRole('menuitemradio', {name: 'Number of events'})
).toBeInTheDocument();
@@ -349,5 +354,38 @@ describe('DataConditionNodeList', () => {
comparison: {value: 11, include: false},
});
});
+
+ it('issue type node', async () => {
+ const condition = DataConditionFixture({
+ id: 'issue-type',
+ type: DataConditionType.ISSUE_TYPE,
+ comparison: {
+ value: IssueType.ERROR,
+ include: true,
+ },
+ });
+
+ render(
+
+
+
+
+
+
+ ,
+ {organization}
+ );
+
+ await userEvent.click(await screen.findByLabelText('Issue type'));
+ await userEvent.click(
+ screen.getByRole('menuitemradio', {name: 'Issue Detected by Metric Monitor'})
+ );
+
+ expect(mockUpdateCondition).toHaveBeenLastCalledWith('issue-type', {
+ comparison: {value: IssueType.METRIC_ISSUE, include: true},
+ });
+ });
});
});
diff --git a/static/app/views/automations/components/dataConditionNodes.tsx b/static/app/views/automations/components/dataConditionNodes.tsx
index d59278a5467e4f..2d31cbeb31a317 100644
--- a/static/app/views/automations/components/dataConditionNodes.tsx
+++ b/static/app/views/automations/components/dataConditionNodes.tsx
@@ -63,6 +63,11 @@ import {
validateIssuePriorityCondition,
} from 'sentry/views/automations/components/actionFilters/issuePriority';
import {IssuePriorityDeescalating} from 'sentry/views/automations/components/actionFilters/issuePriorityDeescalating';
+import {
+ IssueTypeDetails,
+ IssueTypeNode,
+ validateIssueTypeCondition,
+} from 'sentry/views/automations/components/actionFilters/issueType';
import {
LatestAdoptedReleaseDetails,
LatestAdoptedReleaseNode,
@@ -202,6 +207,15 @@ export const dataConditionNodesMap = new Map