Skip to content

Commit

Permalink
Degrade gracefully when email is not configured (#2119)
Browse files Browse the repository at this point in the history
  • Loading branch information
davidmreed committed May 25, 2023
1 parent b3bf1ba commit 3c9d347
Show file tree
Hide file tree
Showing 8 changed files with 98 additions and 35 deletions.
4 changes: 4 additions & 0 deletions config/settings/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -255,12 +255,16 @@ def safe_key() -> str:
SERVER_EMAIL = DEFAULT_FROM_EMAIL
if DEBUG:
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
EMAIL_ENABLED = True
else:
EMAIL_BACKEND = "anymail.backends.mailgun.EmailBackend"
ANYMAIL = {
"MAILGUN_API_KEY": env("MAILGUN_API_KEY", default=""),
"MAILGUN_SENDER_DOMAIN": env("MAILGUN_DOMAIN", default=None),
}
EMAIL_ENABLED = (
ANYMAIL["MAILGUN_API_KEY"] != "" and ANYMAIL["MAILGUN_SENDER_DOMAIN"]
)

DAYS_BEFORE_ORG_EXPIRY_TO_ALERT = env.int("DAYS_BEFORE_ORG_EXPIRY_TO_ALERT", default=3)
ORG_RECHECK_MINUTES = env.int("ORG_RECHECK_MINUTES", default=5)
Expand Down
29 changes: 15 additions & 14 deletions metecho/api/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,20 +140,21 @@ class User(PushMixin, HashIdMixin, AbstractUser):
self_guided_tour_enabled = models.BooleanField(default=True)
self_guided_tour_state = models.JSONField(null=True, blank=True)

def notify(self, subject, body):
# Right now, the only way we notify is via email. In future, we
# may add in-app notifications.

# Escape <>& in case the email gets accidentally rendered as HTML
subject = html.escape(subject, quote=False)
body = html.escape(body, quote=False)
send_mail(
subject,
body,
settings.DEFAULT_FROM_EMAIL,
[self.email],
fail_silently=False,
)
def notify(self, subject: str, body: str):
if settings.EMAIL_ENABLED and self.email:
# Right now, the only way we notify is via email. In future, we
# may add in-app notifications.

# Escape <>& in case the email gets accidentally rendered as HTML
subject = html.escape(subject, quote=False)
body = html.escape(body, quote=False)
send_mail(
subject,
body,
settings.DEFAULT_FROM_EMAIL,
[self.email],
fail_silently=False,
)

def queue_refresh_repositories(self):
from .jobs import refresh_github_repositories_for_user_job
Expand Down
9 changes: 7 additions & 2 deletions metecho/api/tests/jobs.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,9 @@ def test_soft_deleted_model(self, scratch_org_factory):
pytest.param(fixture("task_with_project_factory"), id="Task with Project"),
),
)
def test_good(self, scratch_org_factory, _task_factory):
def test_good(self, scratch_org_factory, _task_factory, settings):
settings.EMAIL_ENABLED = True

scratch_org = scratch_org_factory(
unsaved_changes={"something": 1}, task=_task_factory()
)
Expand Down Expand Up @@ -913,7 +915,10 @@ def test_refreshes_commits(self, project_factory, epic_factory, task_factory):


@pytest.mark.django_db
def test_create_pr(mailoutbox, user_factory, task_factory, git_hub_user_factory):
def test_create_pr(
mailoutbox, user_factory, task_factory, git_hub_user_factory, settings
):
settings.EMAIL_ENABLED = True
user = user_factory()
task = task_factory(assigned_qa=git_hub_user_factory(id=user.github_id))
with ExitStack() as stack:
Expand Down
26 changes: 25 additions & 1 deletion metecho/api/tests/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -853,8 +853,9 @@ def test_queues_reassign(
assert user_reassign_job.delay.called

def test_try_send_assignment_emails(
self, auth_request, mailoutbox, git_hub_collaboration_factory, task
self, auth_request, mailoutbox, git_hub_collaboration_factory, task, settings
):
settings.EMAIL_ENABLED = True
git_hub_collaboration_factory(
permissions={"push": True},
project=task.root_project,
Expand All @@ -874,6 +875,29 @@ def test_try_send_assignment_emails(

assert len(mailoutbox) == 2

def test_try_send_assignment_emails__email_disabled(
self, auth_request, mailoutbox, git_hub_collaboration_factory, task, settings
):
settings.EMAIL_ENABLED = False
git_hub_collaboration_factory(
permissions={"push": True},
project=task.root_project,
user__id=auth_request.user.github_id,
)
data = {
"assigned_dev": auth_request.user.github_id,
"assigned_qa": auth_request.user.github_id,
"should_alert_dev": True,
"should_alert_qa": True,
}
serializer = TaskAssigneeSerializer(
task, data=data, context={"request": auth_request}
)
assert serializer.is_valid(), serializer.errors
serializer.save()

assert len(mailoutbox) == 0


@pytest.mark.django_db
class TestScratchOrgSerializer:
Expand Down
1 change: 1 addition & 0 deletions metecho/context_processors.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,5 +14,6 @@ def env(request):
"SENTRY_DSN": settings.SENTRY_DSN,
"ORG_RECHECK_MINUTES": settings.ORG_RECHECK_MINUTES,
"ENABLE_WALKTHROUGHS": settings.ENABLE_WALKTHROUGHS,
"EMAIL_ENABLED": settings.EMAIL_ENABLED,
}
return {"GLOBALS": GLOBALS}
42 changes: 24 additions & 18 deletions src/js/components/githubUsers/assignTaskRole.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,29 @@ const AssignTaskRoleModal = ({
const alertType =
orgType === ORG_TYPES.DEV ? 'should_alert_dev' : 'should_alert_qa';

const footerComponents = (
window.GLOBALS.EMAIL_ENABLED
? [
<Checkbox
key="alert"
labels={{ label: checkboxLabel }}
className="slds-float_left slds-p-top_xx-small"
name={alertType}
checked={shouldAlertAssignee}
onChange={handleAlertAssignee}
/>,
]
: []
).concat([
<Button key="cancel" label={t('Cancel')} onClick={handleClose} />,
<Button
key="submit"
label={t('Save')}
variant="brand"
disabled={!selection}
onClick={handleSave}
/>,
]);
return (
<Modal
isOpen={isOpen}
Expand All @@ -139,24 +162,7 @@ const AssignTaskRoleModal = ({
directional
size="small"
className="modal-set-height"
footer={[
<Checkbox
key="alert"
labels={{ label: checkboxLabel }}
className="slds-float_left slds-p-top_xx-small"
name={alertType}
checked={shouldAlertAssignee}
onChange={handleAlertAssignee}
/>,
<Button key="cancel" label={t('Cancel')} onClick={handleClose} />,
<Button
key="submit"
label={t('Save')}
variant="brand"
disabled={!selection}
onClick={handleSave}
/>,
]}
footer={footerComponents}
>
{selectedUser && (
<>
Expand Down
11 changes: 11 additions & 0 deletions test/js/components/epics/detail.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -824,6 +824,17 @@ describe('<EpicDetail/>', () => {
});

describe('alerts assigned user', () => {
let EMAIL_ENABLED;

beforeAll(() => {
EMAIL_ENABLED = window.GLOBALS.EMAIL_ENABLED;
window.GLOBALS.EMAIL_ENABLED = true;
});

afterAll(() => {
window.GLOBALS.EMAIL_ENABLED = EMAIL_ENABLED;
});

test('assigning tester', () => {
const { getAllByText, baseElement, getByText } = setup();
fireEvent.click(getAllByText('Assign Tester')[0]);
Expand Down
11 changes: 11 additions & 0 deletions test/js/components/orgs/taskOrgCards.test.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -237,6 +237,17 @@ describe('<TaskOrgCards/>', () => {
});

describe('Assign click', () => {
let EMAIL_ENABLED;

beforeAll(() => {
EMAIL_ENABLED = window.GLOBALS.EMAIL_ENABLED;
window.GLOBALS.EMAIL_ENABLED = true;
});

afterAll(() => {
window.GLOBALS.EMAIL_ENABLED = EMAIL_ENABLED;
});

test('updates assigned user', () => {
const task = {
...defaultTask,
Expand Down

0 comments on commit 3c9d347

Please sign in to comment.