Skip to content

Commit

Permalink
Initial work for #1377, weekly email for managers
Browse files Browse the repository at this point in the history
Send a simple email template with the task histories
on a project to it's manager with a weekly date range
Added a simple test for it and added skips for tests
instead of returning as true.
  • Loading branch information
fitoria committed Apr 19, 2019
1 parent 9e1dac1 commit cdb6814
Show file tree
Hide file tree
Showing 7 changed files with 93 additions and 21 deletions.
10 changes: 9 additions & 1 deletion manage.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,11 @@
from server import create_app, init_counters
from server.services.users.authentication_service import AuthenticationService
from server.services.users.user_service import UserService
from server.services.messaging.message_service import MessageService

import os
import warnings

# Load configuration from file
load_dotenv(os.path.join(os.path.dirname(__file__), 'tasking-manager.env'))

Expand Down Expand Up @@ -55,5 +56,12 @@ def refresh_levels():
print(f'Updated {users_updated} user mapper levels')


@manager.command
def send_weekly_manager_email():
print('Sending weekly email contribution updates to managers ...')
messages_sent = MessageService.send_weekly_managers_email()
print(f'Sent {messages_sent} messages')


if __name__ == '__main__':
manager.run()
10 changes: 5 additions & 5 deletions server/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,14 @@ class EnvironmentConfig:
""" Most settings can be defined through environment variables. """

# Load configuration from file
load_dotenv(os.path.join(os.path.dirname(__file__), 'tasking-manager.env'))
load_dotenv(os.path.join(os.path.dirname(__file__), os.path.pardir, 'tasking-manager.env'))

# The base url the application is reachable
APP_BASE_URL = os.getenv('TM_APP_BASE_URL', 'http://127.0.0.1:5000')

# The default tag used in the OSM changeset comment
DEFAULT_CHANGESET_COMMENT = os.getenv('TM_DEFAULT_CHANGESET_COMMENT', '#tm-project')

# The address to use as the sender on auto generated emails
EMAIL_FROM_ADDRESS = os.getenv('TM_EMAIL_FROM_ADDRESS', None)

Expand All @@ -37,15 +37,15 @@ class EnvironmentConfig:
f'@{POSTGRES_ENDPOINT}:' + \
f'{POSTGRES_PORT}' + \
f'/{POSTGRES_DB}'

# Logging settings
LOG_LEVEL = os.getenv('TM_LOG_LEVEL', logging.DEBUG)
LOG_DIR = os.getenv('TM_LOG_DIR', 'logs')

# Mapper Level values represent number of OSM changesets
MAPPER_LEVEL_INTERMEDIATE = os.getenv('TM_MAPPER_LEVEL_INTERMEDIATE', 250)
MAPPER_LEVEL_ADVANCED = os.getenv('TM_MAPPER_LEVEL_ADVANCED', 500)

# Time to wait until task auto-unlock (e.g. '2h' or '7d' or '30m' or '1h30m')
TASK_AUTOUNLOCK_AFTER = os.getenv('TM_TASK_AUTOUNLOCK_AFTER', '2h')

Expand Down
47 changes: 45 additions & 2 deletions server/services/messaging/message_service.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import re
import time
import datetime

from cachetools import TTLCache, cached
from typing import List
from flask import current_app

from server import create_app
from server import create_app, db
from server.models.dtos.message_dto import MessageDTO
from server.models.postgis.message import Message, NotFound
from server.models.postgis.task import TaskStatus
from server.models.postgis.task import TaskStatus, TaskHistory
from server.models.postgis.project import Project, ProjectStatus
from server.services.messaging.smtp_service import SMTPService
from server.services.messaging.template_service import get_template, get_profile_url
from server.services.project_service import ProjectService
Expand Down Expand Up @@ -153,6 +155,47 @@ def resend_email_validation(user_id: int):
user = UserService.get_user_by_id(user_id)
SMTPService.send_verification_email(user.email_address, user.username)


@staticmethod
def send_weekly_managers_email():
""" Sends a weekly project activity summary for all active projects """
projects = Project.query.filter_by(status=ProjectStatus.PUBLISHED.value).all()
messages_sent = 0
one_week_ago = datetime.datetime.now() - datetime.timedelta(days=7)
for project in projects:
project_info = project.project_info.first()
subject = f'Weekly summary for {project_info.name}'
sent = MessageService._send_managers_project_email(project, subject, one_week_ago)
if sent:
messages_sent += 1
return messages_sent

@staticmethod
def _send_managers_project_email(project: Project, subject: str, min_date: datetime.datetime, max_date=datetime.datetime.now()):
""" Sends a weekly project activity summary. """
project_info = project.project_info.first()
task_histories_query = TaskHistory.query.filter_by(project_id=project.id).filter(TaskHistory.action_date.between(min_date, max_date))

if db.session.query(task_histories_query.exists()).scalar() and project.author.email_address:
message = ''
num_contributions = 0
for task_history in task_histories_query.all():
message += f'{task_history.actioned_by.username} {task_history.action_text} on {task_history.action_date} \n'
num_contributions += 1

context = {
'USERNAME': project.author.username,
'PROJECT_NAME': project_info.name,
'CONTRIBUTION_LIST': message,
'NUM_CONTRIBUTIONS': str(num_contributions),
}

SMTPService.send_templated_email(project.author.email_address, subject, 'weekly_email_managers_en', context)
return True
else:
return False


@staticmethod
def _parse_message_for_username(message: str) -> List[str]:
""" Extracts all usernames from a comment looks for format @[user name] """
Expand Down
14 changes: 14 additions & 0 deletions server/services/messaging/smtp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,20 @@ def send_email_alert(to_address: str, username: str):

return True

@staticmethod
def send_templated_email(to_address: str, subject: str, template_name: str, template_context: dict):
""" Sends a email to an address built from a template with a dictionary as a context """
html_template = get_template(f'{template_name}.html')
text_template = get_template(f'{template_name}.txt')

for key, value in template_context.items():
text_template = text_template.replace(f'[{key}]', value)
html_template = html_template.replace(f'[{key}]', value)

SMTPService._send_mesage(to_address, subject, html_template, text_template)
return True


@staticmethod
def _send_mesage(to_address: str, subject: str, html_message: str, text_message: str):
""" Helper sends SMTP message """
Expand Down
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Hi [USERNAME]

This is the weekly update for your project: [PROJECT_NAME]

[PROJECT_NAME] had [NUM_CONTRIBUTIONS] contributions this week.

[CONTRIBUTION_LIST]
26 changes: 13 additions & 13 deletions tests/server/integration/services/messaging/test_smtp_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@ class TestStatsService(unittest.TestCase):
@classmethod
def setUpClass(cls):
env = os.getenv('CI', 'false')

# Firewall rules mean we can't hit Postgres from CI so we have to skip them in the CI build
if env == 'true':
cls.skip_tests = True
Expand All @@ -25,31 +24,32 @@ def setUp(self):
def tearDown(self):
self.ctx.pop()

@unittest.skipIf(not os.getenv('TM_SMTP_HOST'), 'TM_SMTP_HOST not set')
def test_send_verification_mail(self):
if self.skip_tests:
return

if os.getenv('TM_SMTP_HOST') is None:
return # If SMTP not setup there's no value attempting the integration tests
self.skipTest('CI build')

self.assertTrue(SMTPService.send_verification_email('[email protected]', 'mrtest'))

def test_send_alert(self):
@unittest.skipIf(not os.getenv('TM_SMTP_HOST'), 'TM_SMTP_HOST not set')
def test_send_templated_email(self):
if self.skip_tests:
return
self.skipTest('CI build')

if os.getenv('TM_SMTP_HOST') is None:
return # If SMTP not setup there's no value attempting the integration tests
self.assertTrue(SMTPService.send_templated_email('[email protected]', 'Test send templated email', 'weekly_email_managers_en', {}))

@unittest.skipIf(not os.getenv('TM_SMTP_HOST'), 'TM_SMTP_HOST not set')
def test_send_alert(self):
if self.skip_tests:
self.skipTest('CI build')

self.assertTrue(SMTPService.send_email_alert('[email protected]',
'Iain Hunter'))

@unittest.skipIf(not os.getenv('TM_SMTP_HOST'), 'TM_SMTP_HOST not set')
def test_send_alert_message_limits(self):
if self.skip_tests:
return

if os.getenv('TM_SMTP_HOST') is None:
return # If SMTP not setup there's no value attempting the integration tests
self.skipTest('CI build')

for x in range(0, 50):
self.assertTrue(SMTPService.send_email_alert('[email protected]',
Expand Down

0 comments on commit cdb6814

Please sign in to comment.