Skip to content

Commit 64c0dc8

Browse files
committed
Notify on slack when we receive a new invitation letter request
1 parent 30b411f commit 64c0dc8

File tree

5 files changed

+115
-5
lines changed

5 files changed

+115
-5
lines changed

backend/api/visa/mutations/request_invitation_letter.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from api.utils import validate_email
66
from api.visa.types import InvitationLetterOnBehalfOf, InvitationLetterRequest
77
from api.extensions import RateLimit
8+
from visa.tasks import notify_new_invitation_letter_request_on_slack
89
from conferences.models.deadline import Deadline
910
from privacy_policy.record import record_privacy_policy_acceptance
1011
from visa.models import (
@@ -168,5 +169,9 @@ def request_invitation_letter(
168169
conference,
169170
"invitation_letter",
170171
)
172+
notify_new_invitation_letter_request_on_slack.delay(
173+
invitation_letter_request_id=invitation_letter.id,
174+
admin_absolute_uri=info.context.request.build_absolute_uri("/"),
175+
)
171176

172177
return InvitationLetterRequest.from_model(invitation_letter)

backend/api/visa/tests/mutations/test_request_invitation_letter.py

Lines changed: 29 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,10 @@ def _request_invitation_letter(client, **input):
5050
)
5151

5252

53-
def test_request_invitation_letter(graphql_client, user, mock_has_ticket):
53+
def test_request_invitation_letter(graphql_client, user, mock_has_ticket, mocker):
54+
mock_notify = mocker.patch(
55+
"api.visa.mutations.request_invitation_letter.notify_new_invitation_letter_request_on_slack"
56+
)
5457
conference = ConferenceFactory()
5558
ActiveDeadlineFactory(
5659
conference=conference, type=Deadline.TYPES.invitation_letter_request
@@ -103,10 +106,19 @@ def test_request_invitation_letter(graphql_client, user, mock_has_ticket):
103106
user=user, conference=conference, privacy_policy="invitation_letter"
104107
).exists()
105108

109+
mock_notify.delay.assert_called_once_with(
110+
invitation_letter_request_id=invitation_letter_request.id,
111+
admin_absolute_uri=mocker.ANY,
112+
)
113+
106114

107115
def test_can_request_invitation_letter_to_multiple_conferences(
108-
graphql_client, user, mock_has_ticket
116+
graphql_client, user, mock_has_ticket, mocker
109117
):
118+
mocker.patch(
119+
"api.visa.mutations.request_invitation_letter.notify_new_invitation_letter_request_on_slack"
120+
)
121+
110122
graphql_client.force_login(user)
111123

112124
conference = ConferenceFactory()
@@ -176,8 +188,12 @@ def test_can_request_invitation_letter_to_multiple_conferences(
176188

177189

178190
def test_request_invitation_letter_email_is_ignored_for_self_requests(
179-
graphql_client, user, mock_has_ticket
191+
graphql_client, user, mock_has_ticket, mocker
180192
):
193+
mocker.patch(
194+
"api.visa.mutations.request_invitation_letter.notify_new_invitation_letter_request_on_slack"
195+
)
196+
181197
conference = ConferenceFactory()
182198
mock_has_ticket(conference)
183199
ActiveDeadlineFactory(
@@ -224,8 +240,12 @@ def test_request_invitation_letter_email_is_ignored_for_self_requests(
224240

225241
@pytest.mark.parametrize("has_ticket", [True, False])
226242
def test_request_invitation_letter_on_behalf_of_other(
227-
graphql_client, user, mock_has_ticket, has_ticket
243+
graphql_client, user, mock_has_ticket, has_ticket, mocker
228244
):
245+
mocker.patch(
246+
"api.visa.mutations.request_invitation_letter.notify_new_invitation_letter_request_on_slack"
247+
)
248+
229249
conference = ConferenceFactory()
230250
mock_has_ticket(conference, has_ticket=has_ticket, user=user)
231251
ActiveDeadlineFactory(
@@ -278,8 +298,12 @@ def test_request_invitation_letter_on_behalf_of_other(
278298

279299

280300
def test_duplicate_requests_for_others_are_ignored(
281-
graphql_client, user, mock_has_ticket
301+
graphql_client, user, mock_has_ticket, mocker
282302
):
303+
mocker.patch(
304+
"api.visa.mutations.request_invitation_letter.notify_new_invitation_letter_request_on_slack"
305+
)
306+
283307
conference = ConferenceFactory()
284308
mock_has_ticket(conference, has_ticket=True, user=user)
285309
ActiveDeadlineFactory(

backend/conferences/models/conference.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,12 @@ class Conference(GeoLocalizedModel, TimeFramedModel, TimeStampedModel):
8686
blank=True,
8787
default="",
8888
)
89+
slack_new_invitation_letter_request_channel_id = models.CharField(
90+
_("New invitation letter request Slack channel ID for notification"),
91+
max_length=255,
92+
blank=True,
93+
default="",
94+
)
8995

9096
grants_default_ticket_amount = models.DecimalField(
9197
verbose_name=_("grants default ticket amount"),

backend/visa/tasks.py

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from django.urls import reverse
2+
from integrations import slack
13
import time
24
from django.template import Template, Context
35

@@ -179,3 +181,53 @@ def download_pretix_ticket(invitation_letter_request):
179181
break
180182

181183
return io.BytesIO(response.content)
184+
185+
186+
@app.task
187+
def notify_new_invitation_letter_request_on_slack(
188+
*, invitation_letter_request_id: int, admin_absolute_uri: str
189+
):
190+
invitation_letter_request = InvitationLetterRequest.objects.get(
191+
id=invitation_letter_request_id
192+
)
193+
conference = invitation_letter_request.conference
194+
name = invitation_letter_request.full_name
195+
196+
admin_path = reverse(
197+
"admin:visa_invitationletterrequest_change", args=[invitation_letter_request.id]
198+
)
199+
200+
slack.send_message(
201+
[
202+
{
203+
"type": "section",
204+
"text": {
205+
"text": f"New invitation letter request from {name}",
206+
"type": "plain_text",
207+
},
208+
}
209+
],
210+
[
211+
{
212+
"blocks": [
213+
{
214+
"type": "actions",
215+
"elements": [
216+
{
217+
"type": "button",
218+
"text": {
219+
"type": "plain_text",
220+
"text": "Open in Admin",
221+
"emoji": True,
222+
},
223+
"action_id": "ignore-action",
224+
"url": f"{admin_absolute_uri}{admin_path[1:]}",
225+
}
226+
],
227+
}
228+
]
229+
}
230+
],
231+
oauth_token=conference.get_slack_oauth_token(),
232+
channel_id=conference.slack_new_proposal_channel_id,
233+
)

backend/visa/tests/test_tasks.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import pytest
1010
from django.test import override_settings
1111
from visa.tasks import (
12+
notify_new_invitation_letter_request_on_slack,
1213
process_invitation_letter_request,
1314
process_invitation_letter_request_failed,
1415
)
@@ -352,3 +353,25 @@ def test_process_invitation_letter_request_failed():
352353
request.refresh_from_db()
353354

354355
assert request.status == InvitationLetterRequestStatus.FAILED_TO_GENERATE
356+
357+
358+
def test_notify_new_invitation_letter_request_on_slack(mocker):
359+
mock_slack = mocker.patch("visa.tasks.slack.send_message")
360+
invitation_letter_request = InvitationLetterRequestFactory(
361+
conference__slack_new_invitation_letter_request_channel_id="S123",
362+
organizer__slack_oauth_bot_token="token123",
363+
)
364+
admin_absolute_uri = (
365+
"http://example.com/admin/visa/invitationletterrequest/1/change/"
366+
)
367+
368+
notify_new_invitation_letter_request_on_slack(
369+
invitation_letter_request_id=invitation_letter_request.id,
370+
admin_absolute_uri=admin_absolute_uri,
371+
)
372+
373+
mock_slack.assert_called_once()
374+
375+
kwargs = mock_slack.mock_calls[0][2]
376+
assert kwargs["oauth_token"] == "token123"
377+
assert kwargs["channel_id"] == "S123"

0 commit comments

Comments
 (0)