Skip to content

Commit 9c272df

Browse files
authored
feat(aci): add more action nodes (#91607)
adding more action nodes (azure devops, email, github, github enterprise, jira, jira server, opsgenie, pagerduty, slack) preview [here](https://sentry-73eqbmn2e.sentry.dev/issues/automations/new/settings) <img width="1147" alt="Screenshot 2025-05-13 at 5 28 18 PM" src="https://github.com/user-attachments/assets/e9d8c573-b02f-400a-9194-80514c9975d9" /> ### next to-dos: - still need to refactor the ticketing action modal for the ticketing action nodes to be complete (coming in a future PR) - plugin action - webhook action - sentry app actions
1 parent 9296d34 commit 9c272df

21 files changed

+419
-31
lines changed

static/app/components/workflowEngine/form/automationBuilderInputField.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ export default function AutomationBuilderInputField(props: InputFieldProps) {
1111
flexibleControlStateSize
1212
hideLabel
1313
inline
14-
style={{height: '28px', minHeight: '28px'}}
14+
style={{height: '32px', minHeight: '32px'}}
1515
{...props}
1616
/>
1717
);

static/app/components/workflowEngine/form/automationBuilderSelectField.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,11 +25,11 @@ const StyledSelectField = styled(SelectField)`
2525
}
2626
`;
2727

28-
const selectControlStyles = {
28+
export const selectControlStyles = {
2929
control: (provided: any) => ({
3030
...provided,
31-
minHeight: '28px',
32-
height: '28px',
31+
minHeight: '32px',
32+
height: '32px',
3333
padding: 0,
3434
}),
3535
};

static/app/types/workflowEngine/actions.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,8 @@ export enum ActionType {
2424
export interface Integration {
2525
id: string;
2626
name: string;
27+
services?: Array<{
28+
id: string;
29+
name: string;
30+
}>;
2731
}

static/app/views/automations/components/actionNodes.tsx

Lines changed: 38 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,17 @@ import {createContext, useContext} from 'react';
33
import {t} from 'sentry/locale';
44
import type {Integration, NewAction} from 'sentry/types/workflowEngine/actions';
55
import {ActionType} from 'sentry/types/workflowEngine/actions';
6-
import DiscordNode from 'sentry/views/automations/components/actions/discord';
7-
import MSTeamsNode from 'sentry/views/automations/components/actions/msTeams';
6+
import {AzureDevOpsNode} from 'sentry/views/automations/components/actions/azureDevOps';
7+
import {DiscordNode} from 'sentry/views/automations/components/actions/discord';
8+
import {EmailNode} from 'sentry/views/automations/components/actions/email';
9+
import {GithubNode} from 'sentry/views/automations/components/actions/github';
10+
import {GithubEnterpriseNode} from 'sentry/views/automations/components/actions/githubEnterprise';
11+
import {JiraNode} from 'sentry/views/automations/components/actions/jira';
12+
import {JiraServerNode} from 'sentry/views/automations/components/actions/jiraServer';
13+
import {MSTeamsNode} from 'sentry/views/automations/components/actions/msTeams';
14+
import {OpsgenieNode} from 'sentry/views/automations/components/actions/opsgenie';
15+
import {PagerdutyNode} from 'sentry/views/automations/components/actions/pagerduty';
16+
import {SlackNode} from 'sentry/views/automations/components/actions/slack';
817

918
interface ActionNodeProps {
1019
action: NewAction;
@@ -29,18 +38,42 @@ type ActionNode = {
2938
};
3039

3140
export const actionNodesMap = new Map<ActionType, ActionNode>([
41+
[ActionType.AZURE_DEVOPS, {label: t('Azure DevOps'), action: <AzureDevOpsNode />}],
42+
[ActionType.EMAIL, {label: t('Email'), action: <EmailNode />}],
43+
[
44+
ActionType.DISCORD,
45+
{
46+
label: t('Discord'),
47+
action: <DiscordNode />,
48+
},
49+
],
50+
[ActionType.GITHUB, {label: t('Github'), action: <GithubNode />}],
51+
[
52+
ActionType.GITHUB_ENTERPRISE,
53+
{label: t('Github Enterprise'), action: <GithubEnterpriseNode />},
54+
],
55+
[ActionType.JIRA, {label: t('Jira'), action: <JiraNode />}],
56+
[ActionType.JIRA_SERVER, {label: t('Jira Server'), action: <JiraServerNode />}],
3257
[
3358
ActionType.MSTEAMS,
3459
{
3560
label: t('MS Teams'),
3661
action: <MSTeamsNode />,
3762
},
3863
],
64+
[ActionType.OPSGENIE, {label: t('Opsgenie'), action: <OpsgenieNode />}],
3965
[
40-
ActionType.DISCORD,
66+
ActionType.PAGERDUTY,
4167
{
42-
label: t('Discord'),
43-
action: <DiscordNode />,
68+
label: t('Pagerduty'),
69+
action: <PagerdutyNode />,
70+
},
71+
],
72+
[
73+
ActionType.SLACK,
74+
{
75+
label: t('Slack'),
76+
action: <SlackNode />,
4477
},
4578
],
4679
]);
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
2+
import {tct} from 'sentry/locale';
3+
import {ActionType} from 'sentry/types/workflowEngine/actions';
4+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
5+
import {TicketActionSettingsButton} from 'sentry/views/automations/components/actions/ticketActionSettingsButton';
6+
7+
export function AzureDevOpsNode() {
8+
return tct(
9+
'Create an [logo] Azure DevOps work item in [integration] with these [settings]',
10+
{
11+
logo: ActionMetadata[ActionType.AZURE_DEVOPS]?.icon,
12+
integration: <IntegrationField />,
13+
settings: <TicketActionSettingsButton />,
14+
}
15+
);
16+
}

static/app/views/automations/components/actions/discord.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import {
1212
OptionalRowLine,
1313
} from 'sentry/views/automations/components/automationBuilderRow';
1414

15-
export default function DiscordNode() {
15+
export function DiscordNode() {
1616
return (
1717
<Flex column gap={space(1)} flex="1">
1818
<RowLine>
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
import styled from '@emotion/styled';
2+
3+
import SelectMembers from 'sentry/components/selectMembers';
4+
import TeamSelector from 'sentry/components/teamSelector';
5+
import AutomationBuilderSelectField, {
6+
selectControlStyles,
7+
} from 'sentry/components/workflowEngine/form/automationBuilderSelectField';
8+
import {tct} from 'sentry/locale';
9+
import useOrganization from 'sentry/utils/useOrganization';
10+
import {useActionNodeContext} from 'sentry/views/automations/components/actionNodes';
11+
12+
enum TargetType {
13+
USER = 'user',
14+
TEAM = 'team',
15+
ISSUE_OWNERS = 'issue_owners',
16+
}
17+
18+
enum FallthroughChoiceType {
19+
ALL_MEMBERS = 'AllMembers',
20+
ACTIVE_MEMBERS = 'ActiveMembers',
21+
NO_ONE = 'NoOne',
22+
}
23+
24+
const TARGET_TYPE_CHOICES = [
25+
{value: TargetType.ISSUE_OWNERS, label: 'Suggested assignees'},
26+
{value: TargetType.TEAM, label: 'Team'},
27+
{value: TargetType.USER, label: 'Member'},
28+
];
29+
30+
const FALLTHROUGH_CHOICES = [
31+
{value: FallthroughChoiceType.ACTIVE_MEMBERS, label: 'Recently Active Members'},
32+
{value: FallthroughChoiceType.ALL_MEMBERS, label: 'All Project Members'},
33+
{value: FallthroughChoiceType.NO_ONE, label: 'No One'},
34+
];
35+
36+
export function EmailNode() {
37+
return tct('Notify [targetType] [identifier]', {
38+
targetType: <TargetTypeField />,
39+
identifier: <IdentifierField />,
40+
});
41+
}
42+
43+
function TargetTypeField() {
44+
const {action, actionId, onUpdate} = useActionNodeContext();
45+
return (
46+
<AutomationBuilderSelectField
47+
name={`${actionId}.data.targetType`}
48+
value={action.data.targetType}
49+
options={TARGET_TYPE_CHOICES}
50+
onChange={(value: string) => onUpdate({targetType: value})}
51+
/>
52+
);
53+
}
54+
55+
function IdentifierField() {
56+
const {action, actionId, onUpdate} = useActionNodeContext();
57+
const organization = useOrganization();
58+
59+
if (action.data.targetType === TargetType.TEAM) {
60+
return (
61+
<SelectWrapper>
62+
<TeamSelector
63+
name={`${actionId}.data.targetIdentifier`}
64+
value={action.data.targetIdentifier}
65+
onChange={(value: any) => onUpdate({targetIdentifier: value})}
66+
useId
67+
styles={selectControlStyles}
68+
/>
69+
</SelectWrapper>
70+
);
71+
}
72+
if (action.data.targetType === TargetType.USER) {
73+
return (
74+
<SelectWrapper>
75+
<SelectMembers
76+
organization={organization}
77+
key={`${actionId}.data.targetIdentifier`}
78+
value={action.data.targetIdentifier}
79+
onChange={(value: any) => onUpdate({targetIdentifier: value})}
80+
styles={selectControlStyles}
81+
/>
82+
</SelectWrapper>
83+
);
84+
}
85+
return tct('and, if none found, notify [fallThrough]', {
86+
fallThrough: <FallthroughField />,
87+
});
88+
}
89+
90+
function FallthroughField() {
91+
const {action, actionId, onUpdate} = useActionNodeContext();
92+
return (
93+
<AutomationBuilderSelectField
94+
name={`${actionId}.data.fallthroughType`}
95+
value={action.data.fallthroughType}
96+
options={FALLTHROUGH_CHOICES}
97+
onChange={(value: string) => onUpdate({fallthroughType: value})}
98+
/>
99+
);
100+
}
101+
102+
const SelectWrapper = styled('div')`
103+
width: 200px;
104+
`;
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
2+
import {tct} from 'sentry/locale';
3+
import {ActionType} from 'sentry/types/workflowEngine/actions';
4+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
5+
import {TicketActionSettingsButton} from 'sentry/views/automations/components/actions/ticketActionSettingsButton';
6+
7+
export function GithubNode() {
8+
return tct('Create a [logo] GitHub issue in [integration] with these [settings]', {
9+
logo: ActionMetadata[ActionType.GITHUB]?.icon,
10+
integration: <IntegrationField />,
11+
settings: <TicketActionSettingsButton />,
12+
});
13+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
2+
import {tct} from 'sentry/locale';
3+
import {ActionType} from 'sentry/types/workflowEngine/actions';
4+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
5+
import {TicketActionSettingsButton} from 'sentry/views/automations/components/actions/ticketActionSettingsButton';
6+
7+
export function GithubEnterpriseNode() {
8+
return tct(
9+
'Create a [logo] GitHub Enterprise issue in [integration] with these [settings]',
10+
{
11+
logo: ActionMetadata[ActionType.GITHUB_ENTERPRISE]?.icon,
12+
integration: <IntegrationField />,
13+
settings: <TicketActionSettingsButton />,
14+
}
15+
);
16+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
2+
import {tct} from 'sentry/locale';
3+
import {ActionType} from 'sentry/types/workflowEngine/actions';
4+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
5+
import {TicketActionSettingsButton} from 'sentry/views/automations/components/actions/ticketActionSettingsButton';
6+
7+
export function JiraNode() {
8+
return tct('Create a [logo] Jira issue in [integration] with these [settings]', {
9+
logo: ActionMetadata[ActionType.JIRA]?.icon,
10+
integration: <IntegrationField />,
11+
settings: <TicketActionSettingsButton />,
12+
});
13+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
2+
import {tct} from 'sentry/locale';
3+
import {ActionType} from 'sentry/types/workflowEngine/actions';
4+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
5+
import {TicketActionSettingsButton} from 'sentry/views/automations/components/actions/ticketActionSettingsButton';
6+
7+
export function JiraServerNode() {
8+
return tct('Create a [logo] Jira Server issue in {integration} with these [settings]', {
9+
logo: ActionMetadata[ActionType.JIRA_SERVER]?.icon,
10+
integration: <IntegrationField />,
11+
settings: <TicketActionSettingsButton />,
12+
});
13+
}

static/app/views/automations/components/actions/msTeams.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ import {IntegrationField} from 'sentry/views/automations/components/actions/inte
44
import {TargetDisplayField} from 'sentry/views/automations/components/actions/targetDisplayField';
55
import {ICON_SIZE} from 'sentry/views/automations/components/automationBuilderRow';
66

7-
export default function MSTeamsNode() {
7+
export function MSTeamsNode() {
88
return tct('Send a [logo] Microsoft Teams notification to [team] Team, to [channel]', {
99
logo: <PluginIcon pluginId="msteams" size={ICON_SIZE} />,
1010
team: <IntegrationField />,
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import AutomationBuilderSelectField from 'sentry/components/workflowEngine/form/automationBuilderSelectField';
2+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
3+
import {tct} from 'sentry/locale';
4+
import {ActionType} from 'sentry/types/workflowEngine/actions';
5+
import {useActionNodeContext} from 'sentry/views/automations/components/actionNodes';
6+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
7+
import {ServiceField} from 'sentry/views/automations/components/actions/serviceField';
8+
9+
const OPSGENIE_PRIORITIES = ['P1', 'P2', 'P3', 'P4', 'P5'];
10+
11+
export function OpsgenieNode() {
12+
return tct(
13+
'Send a [logo] Opsgenie notification to [account] and team [team] with [priority] priority',
14+
{
15+
logo: ActionMetadata[ActionType.OPSGENIE]?.icon,
16+
account: <IntegrationField />,
17+
team: <ServiceField />,
18+
priority: <PriorityField />,
19+
}
20+
);
21+
}
22+
23+
function PriorityField() {
24+
const {action, actionId, onUpdate} = useActionNodeContext();
25+
return (
26+
<AutomationBuilderSelectField
27+
name={`${actionId}.data.priority`}
28+
value={action.data.priority}
29+
options={OPSGENIE_PRIORITIES.map(priority => ({
30+
label: priority,
31+
value: priority,
32+
}))}
33+
onChange={(value: string) => {
34+
onUpdate({
35+
priority: value,
36+
});
37+
}}
38+
/>
39+
);
40+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import AutomationBuilderSelectField from 'sentry/components/workflowEngine/form/automationBuilderSelectField';
2+
import {ActionMetadata} from 'sentry/components/workflowEngine/ui/actionMetadata';
3+
import {tct} from 'sentry/locale';
4+
import {ActionType} from 'sentry/types/workflowEngine/actions';
5+
import {useActionNodeContext} from 'sentry/views/automations/components/actionNodes';
6+
import {IntegrationField} from 'sentry/views/automations/components/actions/integrationField';
7+
import {ServiceField} from 'sentry/views/automations/components/actions/serviceField';
8+
9+
const PAGERDUTY_SEVERITIES = ['default', 'critical', 'warning', 'error', 'info'];
10+
11+
export function PagerdutyNode() {
12+
return tct(
13+
'Send a [logo] PagerDuty notification to [account] and service [service] with [severity] severity',
14+
{
15+
logo: ActionMetadata[ActionType.PAGERDUTY]?.icon,
16+
account: <IntegrationField />,
17+
service: <ServiceField />,
18+
severity: <SeverityField />,
19+
}
20+
);
21+
}
22+
23+
function SeverityField() {
24+
const {action, actionId, onUpdate} = useActionNodeContext();
25+
return (
26+
<AutomationBuilderSelectField
27+
name={`${actionId}.data.severity`}
28+
value={action.data.severity}
29+
options={PAGERDUTY_SEVERITIES.map(severity => ({
30+
label: severity,
31+
value: severity,
32+
}))}
33+
onChange={(value: string) => {
34+
onUpdate({
35+
severity: value,
36+
});
37+
}}
38+
/>
39+
);
40+
}

0 commit comments

Comments
 (0)