-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
/
form.py
154 lines (130 loc) · 6.14 KB
/
form.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
from __future__ import annotations
import logging
from dataclasses import asdict
from typing import Any
from django import forms
from django.core.exceptions import ValidationError
from django.utils.translation import gettext_lazy as _
from sentry.integrations.services.integration import integration_service
from sentry.integrations.slack.utils import (
SLACK_RATE_LIMITED_MESSAGE,
strip_channel_name,
validate_channel_id,
)
from sentry.shared_integrations.exceptions import ApiRateLimitedError, DuplicateDisplayNameError
logger = logging.getLogger("sentry.rules")
class SlackNotifyServiceForm(forms.Form):
workspace = forms.ChoiceField(choices=(), widget=forms.Select())
channel = forms.CharField(widget=forms.TextInput())
channel_id = forms.CharField(required=False, widget=forms.TextInput())
tags = forms.CharField(required=False, widget=forms.TextInput())
def __init__(self, *args: Any, **kwargs: Any) -> None:
# NOTE: Workspace maps directly to the integration ID
workspace_list = [(i.id, i.name) for i in kwargs.pop("integrations")]
self.channel_transformer = kwargs.pop("channel_transformer")
super().__init__(*args, **kwargs)
if workspace_list:
self.fields["workspace"].initial = workspace_list[0][0]
self.fields["workspace"].choices = workspace_list
self.fields["workspace"].widget.choices = self.fields["workspace"].choices
# XXX(meredith): When this gets set to True, it lets the RuleSerializer
# know to only save if and when we have the channel_id. The rule will get saved
# in the task (integrations/slack/tasks.py) if the channel_id is found.
self._pending_save = False
def _format_slack_error_message(self, message: str) -> Any:
return _(f"Slack: {message}")
def clean(self) -> dict[str, Any] | None:
channel_id = (
self.data.get("inputChannelId")
or self.data.get("input_channel_id")
or self.data.get("channel_id")
)
if channel_id:
logger.info(
"rule.slack.provide_channel_id",
extra={
"slack_integration_id": self.data.get("workspace"),
"channel_id": self.data.get("channel_id"),
},
)
if not self.data.get("channel"):
raise forms.ValidationError(
self._format_slack_error_message("Channel name is a required field."),
code="invalid",
)
# default to "#" if they have the channel name without the prefix
channel_prefix = self.data["channel"][0] if self.data["channel"][0] == "@" else "#"
cleaned_data: dict[str, Any] = super().clean()
assert cleaned_data is not None
workspace = cleaned_data.get("workspace")
if not workspace:
raise forms.ValidationError(
self._format_slack_error_message("Workspace is a required field."), code="invalid"
)
if channel_id:
try:
validate_channel_id(
self.data["channel"],
integration_id=workspace,
input_channel_id=channel_id,
)
except ValidationError as e:
params = {"channel": self.data.get("channel"), "channel_id": channel_id}
raise forms.ValidationError(
# ValidationErrors contain a list of error messages, not just one.
self._format_slack_error_message("; ".join(e.messages)),
code="invalid",
params=params,
)
integration = integration_service.get_integration(integration_id=workspace)
if not integration:
raise forms.ValidationError(
self._format_slack_error_message("Workspace is a required field."),
code="invalid",
)
channel = cleaned_data.get("channel", "")
timed_out = False
channel_prefix = ""
# XXX(meredith): If the user is creating/updating a rule via the API and provides
# the channel_id in the request, we don't need to call the channel_transformer - we
# are assuming that they passed in the correct channel_id for the channel
if not channel_id:
try:
channel_data = self.channel_transformer(integration, channel)
channel_prefix, channel_id, timed_out = asdict(channel_data).values()
except DuplicateDisplayNameError:
domain = integration.metadata["domain_name"]
params = {"channel": channel, "domain": domain}
raise forms.ValidationError(
self._format_slack_error_message(
"Multiple users were found with display name '%(channel)s'. "
"Please use your username, found at %(domain)s/account/settings#username."
),
code="invalid",
params=params,
)
except ApiRateLimitedError:
raise forms.ValidationError(
self._format_slack_error_message(SLACK_RATE_LIMITED_MESSAGE),
code="invalid",
)
channel = strip_channel_name(channel)
if channel_id is None and timed_out:
cleaned_data["channel"] = channel_prefix + channel
self._pending_save = True
return cleaned_data
if channel_id is None and workspace is not None:
params = {
"channel": channel,
"workspace": dict(self.fields["workspace"].choices).get(int(workspace)),
}
raise forms.ValidationError(
self._format_slack_error_message(
'The resource "%(channel)s" does not exist or has not been granted access in the %(workspace)s Slack workspace.'
),
code="invalid",
params=params,
)
cleaned_data["channel"] = channel_prefix + channel
cleaned_data["channel_id"] = channel_id
return cleaned_data