From d13201962449c2ad1aae7928415596ec6e9c11bf Mon Sep 17 00:00:00 2001 From: Valentin Deniaud Date: Wed, 7 Oct 2020 11:31:03 +0200 Subject: [PATCH] ovh: send email credit alerts (#42921) --- .../ovh/migrations/0011_auto_20201008_1731.py | 31 ++++++++ passerelle/apps/ovh/models.py | 53 ++++++++++--- .../ovh/templates/ovh/credit_alert_body.html | 22 ++++++ .../ovh/templates/ovh/credit_alert_body.txt | 17 ++++ .../templates/ovh/credit_alert_subject.txt | 6 ++ passerelle/settings.py | 2 + passerelle/templates/emails/body_base.html | 15 ++++ passerelle/templates/emails/body_base.txt | 1 + passerelle/templates/emails/button-link.html | 1 + passerelle/templates/emails/subject.txt | 1 + tests/test_sms.py | 79 +++++++++++++++++++ 11 files changed, 217 insertions(+), 11 deletions(-) create mode 100644 passerelle/apps/ovh/migrations/0011_auto_20201008_1731.py create mode 100644 passerelle/apps/ovh/templates/ovh/credit_alert_body.html create mode 100644 passerelle/apps/ovh/templates/ovh/credit_alert_body.txt create mode 100644 passerelle/apps/ovh/templates/ovh/credit_alert_subject.txt create mode 100644 passerelle/templates/emails/body_base.html create mode 100644 passerelle/templates/emails/body_base.txt create mode 100644 passerelle/templates/emails/button-link.html create mode 100644 passerelle/templates/emails/subject.txt diff --git a/passerelle/apps/ovh/migrations/0011_auto_20201008_1731.py b/passerelle/apps/ovh/migrations/0011_auto_20201008_1731.py new file mode 100644 index 00000000..f05e1707 --- /dev/null +++ b/passerelle/apps/ovh/migrations/0011_auto_20201008_1731.py @@ -0,0 +1,31 @@ +# -*- coding: utf-8 -*- +# Generated by Django 1.11.18 on 2020-10-08 15:31 +from __future__ import unicode_literals + +import django.contrib.postgres.fields +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('ovh', '0010_auto_20201008_1126'), + ] + + operations = [ + migrations.AddField( + model_name='ovhsmsgateway', + name='alert_emails', + field=django.contrib.postgres.fields.ArrayField(base_field=models.EmailField(blank=True, max_length=254), blank=True, null=True, size=None, verbose_name='Email addresses to send credit alerts to'), + ), + migrations.AddField( + model_name='ovhsmsgateway', + name='credit_alert_timestamp', + field=models.DateTimeField(null=True), + ), + migrations.AlterField( + model_name='ovhsmsgateway', + name='credit_threshold_alert', + field=models.PositiveIntegerField(default=500, verbose_name='Credit alert threshold'), + ), + ] diff --git a/passerelle/apps/ovh/models.py b/passerelle/apps/ovh/models.py index fbf20131..91e01bcd 100644 --- a/passerelle/apps/ovh/models.py +++ b/passerelle/apps/ovh/models.py @@ -2,9 +2,15 @@ import hashlib import json import requests import time +from datetime import timedelta from urllib.parse import urljoin +from django.contrib.postgres.fields import ArrayField +from django.conf import settings +from django.core.mail import send_mail from django.db import models +from django.template.loader import render_to_string +from django.utils import timezone from django.utils.encoding import force_text from django.utils.translation import ugettext_lazy as _ @@ -69,8 +75,15 @@ class OVHSMSGateway(SMSResource): msg_class = models.IntegerField(choices=MESSAGES_CLASSES, default=1, verbose_name=_('Message class')) credit_threshold_alert = models.PositiveIntegerField(verbose_name=_('Credit alert threshold'), - default=100) + default=500) credit_left = models.PositiveIntegerField(verbose_name=_('Credit left'), default=0, editable=False) + alert_emails = ArrayField( + models.EmailField(blank=True), + blank=True, + null=True, + verbose_name=_('Email addresses to send credit alerts to'), + ) + credit_alert_timestamp = models.DateTimeField(null=True) TEST_DEFAULTS = { 'create_kwargs': { @@ -174,12 +187,7 @@ class OVHSMSGateway(SMSResource): if self.credit_left < 0: self.credit_left = 0 self.save(update_credit=False) - if self.credit_left < self.credit_threshold_alert: - ret['warning'] = 'credit level too low for %s: %s (threshold %s)' % ( - self.slug, - self.credit_left, - self.credit_threshold_alert, - ) + self.send_credit_alert_if_needed() ret['credit_left'] = self.credit_left ret['ovh_result'] = result ret['sms_ids'] = result.get('ids', []) @@ -187,19 +195,42 @@ class OVHSMSGateway(SMSResource): return ret def update_credit_left(self): - if not self.uses_new_api: - return result = self.request('get', endpoint='') self.credit_left = result['creditsLeft'] self.save(update_credit=False) + def send_credit_alert_if_needed(self): + if self.credit_left >= self.credit_threshold_alert: + return + if self.credit_alert_timestamp and self.credit_alert_timestamp > timezone.now() - timedelta(days=1): + return # alerts are sent daily + ctx = { + 'connector': self, + 'connector_url': urljoin(settings.SITE_BASE_URL, self.get_absolute_url()), + } + subject = render_to_string('ovh/credit_alert_subject.txt', ctx).strip() + body = render_to_string('ovh/credit_alert_body.txt', ctx) + html_body = render_to_string('ovh/credit_alert_body.html', ctx) + send_mail( + _('OVH SMS alert: only %s credits left') % self.credit_left, + body, + settings.DEFAULT_FROM_EMAIL, + self.alert_emails, + html_message=html_body, + ) + self.credit_alert_timestamp = timezone.now() + self.save() + self.logger.warning('credit is too low, alerts were sent to %s', self.alert_emails) + def hourly(self): super().hourly() - self.update_credit_left() + if self.uses_new_api: + self.update_credit_left() + self.send_credit_alert_if_needed() def save(self, *args, update_credit=True, **kwargs): super().save(*args, **kwargs) - if update_credit: + if update_credit and self.uses_new_api: self.add_job('update_credit_left') def send_msg_legacy(self, text, sender, destinations, **kwargs): diff --git a/passerelle/apps/ovh/templates/ovh/credit_alert_body.html b/passerelle/apps/ovh/templates/ovh/credit_alert_body.html new file mode 100644 index 00000000..1b5fa129 --- /dev/null +++ b/passerelle/apps/ovh/templates/ovh/credit_alert_body.html @@ -0,0 +1,22 @@ +{% extends "emails/body_base.html" %} +{% load i18n %} + +{% block content %} +

{% trans "Hi," %}

+ +

+{% blocktrans trimmed with name=connector.title credit_left=connector.credit_left %} +There are only {{ credit_left }} credits left for connector {{ name }}. +{% endblocktrans %} +

+ +

+{% blocktrans trimmed with account=connector.account %} +Please add more credit as soon as possible for OVH account {{ account }}. +{% endblocktrans %} +

+ +{% with _("View connector page") as button_label %} +{% include "emails/button-link.html" with url=connector_url label=button_label %} +{% endwith %} +{% endblock %} diff --git a/passerelle/apps/ovh/templates/ovh/credit_alert_body.txt b/passerelle/apps/ovh/templates/ovh/credit_alert_body.txt new file mode 100644 index 00000000..0a4a062e --- /dev/null +++ b/passerelle/apps/ovh/templates/ovh/credit_alert_body.txt @@ -0,0 +1,17 @@ +{% extends "emails/body_base.txt" %} +{% load i18n %} + +{% block content %}{% autoescape off %}{% trans "Hi," %} + +{% blocktrans trimmed with name=connector.title credit_left=connector.credit_left %} +There are only {{ credit_left }} credits left for connector {{ name }}. +{% endblocktrans %} + +{% blocktrans trimmed with account=connector.account %} +Please add more credit as soon as possible for OVH account {{ account }}. +{% endblocktrans %} + +{% trans "View connector page:" %} {{ connector_url }} + +{% endautoescape %} +{% endblock %} diff --git a/passerelle/apps/ovh/templates/ovh/credit_alert_subject.txt b/passerelle/apps/ovh/templates/ovh/credit_alert_subject.txt new file mode 100644 index 00000000..8c0cdc55 --- /dev/null +++ b/passerelle/apps/ovh/templates/ovh/credit_alert_subject.txt @@ -0,0 +1,6 @@ +{% extends "emails/subject.txt" %} +{% load i18n %} + +{% block email-subject %}{% autoescape off %}{% blocktrans trimmed with credit_left=connector.credit_left %} +OVH SMS alert: only {{ credit_left }} credits left +{% endblocktrans %}{% endautoescape %}{% endblock %} diff --git a/passerelle/settings.py b/passerelle/settings.py index a2a9de1a..0522ce11 100644 --- a/passerelle/settings.py +++ b/passerelle/settings.py @@ -210,6 +210,8 @@ REQUESTS_TIMEOUT = 25 # Passerelle can receive big requests (for example base64 encoded files) DATA_UPLOAD_MAX_MEMORY_SIZE = 100*1024*1024 +SITE_BASE_URL = 'http://localhost' + # List of passerelle.utils.Request response Content-Type to log LOGGED_CONTENT_TYPES_MESSAGES = ( r'text/', r'application/(json|xml)' diff --git a/passerelle/templates/emails/body_base.html b/passerelle/templates/emails/body_base.html new file mode 100644 index 00000000..9705c361 --- /dev/null +++ b/passerelle/templates/emails/body_base.html @@ -0,0 +1,15 @@ + + + + + + +
+
+ {% block content %} + {{ content }} + {% endblock %} +
+
+ + diff --git a/passerelle/templates/emails/body_base.txt b/passerelle/templates/emails/body_base.txt new file mode 100644 index 00000000..15e8e85d --- /dev/null +++ b/passerelle/templates/emails/body_base.txt @@ -0,0 +1 @@ +{% block content %}{{ content }}{% endblock %} diff --git a/passerelle/templates/emails/button-link.html b/passerelle/templates/emails/button-link.html new file mode 100644 index 00000000..2cb07cb8 --- /dev/null +++ b/passerelle/templates/emails/button-link.html @@ -0,0 +1 @@ +{{ label }} diff --git a/passerelle/templates/emails/subject.txt b/passerelle/templates/emails/subject.txt new file mode 100644 index 00000000..2764059b --- /dev/null +++ b/passerelle/templates/emails/subject.txt @@ -0,0 +1 @@ +{% block email-subject %}{% endblock %} diff --git a/tests/test_sms.py b/tests/test_sms.py index 23472f15..d0557150 100644 --- a/tests/test_sms.py +++ b/tests/test_sms.py @@ -259,3 +259,82 @@ def test_ovh_new_api_credit(app, freezer): with utils.mock_url(ovh_url, resp, 200) as mocked: connector.hourly() assert connector.credit_left == 456 + + +def test_ovh_alert_emails(app, freezer, mailoutbox): + connector = OVHSMSGateway.objects.create( + slug='test-ovh', title='Test OVH', account='sms-test42', + application_key='RHrTdU2oTsrVC0pu', + application_secret='CLjtS69tTcPgCKxedeoZlgMSoQGSiXMa', + consumer_key='iF0zi0MJrbjNcI3hvuvwkhNk8skrigxz', + credit_threshold_alert=100, + credit_left=102, + alert_emails=['test@entrouvert.org'], + ) + api = ApiUser.objects.create(username='apiuser') + obj_type = ContentType.objects.get_for_model(connector) + AccessRight.objects.create(codename='can_send_messages', apiuser=api, resource_type=obj_type, + resource_pk=connector.pk) + + payload = { + 'message': 'hello', + 'from': '+33699999999', + 'to': ['+33688888888', '+33677777777'], + } + + # register job + freezer.move_to('2019-01-01 00:00:00') + path = '/%s/%s/send/' % (connector.get_connector_slug(), connector.slug) + app.post_json(path, params=payload) + + # perform job + freezer.move_to('2019-01-01 01:00:03') + resp = { + 'totalCreditsRemoved': 1, + } + base_url = connector.API_URL % {'serviceName': 'sms-test42'} + url = base_url + 'jobs/' + with utils.mock_url(url, resp, 200) as mocked: + connector.jobs() + + assert connector.credit_left == 101 + assert len(mailoutbox) == 0 + + # register job + app.post_json(path, params=payload) + + # perform job + freezer.move_to('2019-01-01 01:00:06') + resp = { + 'totalCreditsRemoved': 2, + } + with utils.mock_url(url, resp, 200) as mocked: + connector.jobs() + + mail = mailoutbox[0] + assert mail.recipients() == ['test@entrouvert.org'] + assert mail.subject == 'OVH SMS alert: only 99 credits left' + for body in (mail.body, mail.alternatives[0][0]): + assert connector.account in body + assert connector.title in body + assert 'http://localhost/ovh/test-ovh/' in body + mailoutbox.clear() + + # alert should not be send with every SMS + app.post_json(path, params=payload) + freezer.move_to('2019-01-01 01:00:09') + with utils.mock_url(url, resp, 200) as mocked: + connector.jobs() + assert len(mailoutbox) == 0 + + # alert is sent again daily + resp = {'creditsLeft': 99} + ovh_url = connector.API_URL % {'serviceName': 'sms-test42'} + with utils.mock_url(ovh_url, resp, 200) as mocked: + connector.hourly() + assert len(mailoutbox) == 0 + + freezer.move_to('2019-01-02 01:00:07') + with utils.mock_url(ovh_url, resp, 200) as mocked: + connector.hourly() + assert len(mailoutbox) == 1 -- 2.20.1