Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ENG-2280] Adds storage limit status messaging to file notifications #9561

Open
wants to merge 6 commits into
base: develop
Choose a base branch
from
Open
100 changes: 99 additions & 1 deletion tests/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,15 @@
AddonFileCopied, AddonFileMoved, AddonFileRenamed,
)
from website.notifications.events import utils
from website import settings
from addons.base import signals
from api.caching.utils import storage_usage_cache
from api.caching import settings as cache_settings
from framework.auth import Auth
from osf_tests import factories
from osf.utils.permissions import WRITE
from osf.utils.permissions import ADMIN, WRITE
from tests.base import OsfTestCase, NotificationTestCase
from osf.models import NotificationDigest

email_transactional = 'email_transactional'
email_digest = 'email_digest'
Expand Down Expand Up @@ -183,6 +187,100 @@ def test_file_updated(self, mock_notify):
assert_true(mock_notify.called)


class TestFileUpdatesStorageMessaging(OsfTestCase):
def setUp(self):
super().setUp()
self.creator = factories.AuthUserFactory()
self.write_contrib = factories.AuthUserFactory()
self.admin_contrib = factories.AuthUserFactory()
self.project = factories.ProjectFactory(creator=self.creator)
self.project.add_contributor(self.write_contrib, permissions=WRITE)
self.project.add_contributor(self.admin_contrib, permissions=ADMIN)
self.storage_usage_key = cache_settings.STORAGE_USAGE_KEY.format(target_id=self.project._id)
self.sub = factories.NotificationSubscriptionFactory(
_id=self.project._id + 'file_updated',
owner=self.project,
event_name='file_updated',
)
self.sub.save()

def test_storage_usage_notification_private_project(self):
storage_usage_cache.set(self.storage_usage_key, 0)
event = event_registry['file_updated'](self.creator, self.project, 'file_updated', payload=file_payload)
assert event.storage_limit_context is None
event.perform()

# A part of the rendered storage usage message if the recipient is an admin, the project is private, and the project is approaching/over storage limits
admin_sub_message = 'the 5GB OSF Storage limit and requires your attention.'
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.admin_contrib.id).message
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.write_contrib.id).message

def test_storage_usage_notification_private_project_approaching(self):
storage_usage_cache.set(self.storage_usage_key, (settings.STORAGE_LIMIT_PRIVATE * settings.GBs)-1)
event = event_registry['file_updated'](self.creator, self.project, 'file_updated', payload=file_payload)
assert_equal(event.storage_limit_context, {'public': False, 'storage_limit_status': 'approaching'})
event.perform()

# A part of the rendered storage usage message if the recipient is an admin, the project is private, and the project is approaching storage limits
admin_sub_message = 'This private project is approaching the 5GB OSF Storage limit and requires your attention.'
assert admin_sub_message in NotificationDigest.objects.get(user_id=self.admin_contrib.id).message
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.write_contrib.id).message


def test_storage_usage_notification_private_project_over(self):
storage_usage_cache.set(self.storage_usage_key, (settings.STORAGE_LIMIT_PRIVATE * settings.GBs)+1)
event = event_registry['file_updated'](self.creator, self.project, 'file_updated', payload=file_payload)
assert_equal(event.storage_limit_context, {'public': False, 'storage_limit_status': 'over'})
event.perform()

# A part of the rendered storage usage message if the recipient is an admin, the project is private, and the project is over storage limits
admin_sub_message = 'This private project is over the 5GB OSF Storage limit and requires your attention.'
assert admin_sub_message in NotificationDigest.objects.get(user_id=self.admin_contrib.id).message
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.write_contrib.id).message

def test_storage_usage_notification_public_project(self):
self.project.is_public = True
self.project.save()

storage_usage_cache.set(self.storage_usage_key, 0)
event = event_registry['file_updated'](self.creator, self.project, 'file_updated', payload=file_payload)
assert event.storage_limit_context is None
event.perform()

# A part of the rendered storage usage message if the recipient is an admin, the project is public, and the project is approaching/over storage limits
admin_sub_message = 'the 50GB OSF Storage limit and requires your attention.'
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.admin_contrib.id).message
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.write_contrib.id).message

def test_storage_usage_notification_public_project_approaching(self):
self.project.is_public = True
self.project.save()

storage_usage_cache.set(self.storage_usage_key, (settings.STORAGE_LIMIT_PUBLIC * settings.GBs)-1)
event = event_registry['file_updated'](self.creator, self.project, 'file_updated', payload=file_payload)
assert_equal(event.storage_limit_context, {'public': True, 'storage_limit_status': 'approaching'})
event.perform()

# A part of the rendered storage usage message if the recipient is an admin, the project is private, and the project is approaching storage limits
admin_sub_message = 'This public project is approaching the 50GB OSF Storage limit and requires your attention.'
assert admin_sub_message in NotificationDigest.objects.get(user_id=self.admin_contrib.id).message
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.write_contrib.id).message

def test_storage_usage_notification_public_project_over(self):
self.project.is_public = True
self.project.save()

storage_usage_cache.set(self.storage_usage_key, (settings.STORAGE_LIMIT_PUBLIC * settings.GBs)+1)
event = event_registry['file_updated'](self.creator, self.project, 'file_updated', payload=file_payload)
assert_equal(event.storage_limit_context, {'public': True, 'storage_limit_status': 'over'})
event.perform()

# A part of the rendered storage usage message if the recipient is an admin, the project is public, and the project is over storage limits
admin_sub_message = 'This public project is over the 50GB OSF Storage limit and requires your attention.'
assert admin_sub_message in NotificationDigest.objects.get(user_id=self.admin_contrib.id).message
assert admin_sub_message not in NotificationDigest.objects.get(user_id=self.write_contrib.id).message


class TestFileAdded(NotificationTestCase):
def setUp(self):
super(TestFileAdded, self).setUp()
Expand Down
1 change: 1 addition & 0 deletions website/notifications/emails.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,7 @@ def store_emails(recipient_ids, notification_type, event, user, node, timestamp,
continue
context['localized_timestamp'] = localize_timestamp(timestamp, recipient)
context['recipient'] = recipient
context['admin_recipient'] = node.has_permission(recipient, ADMIN, check_parent=False)
message = mails.render_message(template, **context)
digest = NotificationDigest(
timestamp=timestamp,
Expand Down
40 changes: 40 additions & 0 deletions website/notifications/events/files.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
register, Event, event_registry, RegistryError
)
from website.notifications.events import utils as event_utils
from website.settings import StorageLimits
from osf.models import AbstractNode, NodeLog, Preprint
from addons.base.signals import file_updated as signal

Expand All @@ -37,6 +38,7 @@ def __init__(self, user, node, event, payload=None):
super(FileEvent, self).__init__(user, node, event)
self.payload = payload
self._url = None
self.storage_limit_status = node.storage_limit_status

@property
def html_message(self):
Expand All @@ -62,6 +64,32 @@ def text_message(self):
name=self.payload['metadata']['materialized'].lstrip('/')
)

@property
def storage_limit_context(self):
node = self.node
if node.is_public:
if node.storage_limit_status is StorageLimits.APPROACHING_PUBLIC:
return {
'public': True,
'storage_limit_status': 'approaching'
}
elif node.storage_limit_status is StorageLimits.OVER_PUBLIC:
return {
'public': True,
'storage_limit_status': 'over'
}
else:
if node.storage_limit_status is StorageLimits.APPROACHING_PRIVATE:
return {
'public': False,
'storage_limit_status': 'approaching'
}
elif node.storage_limit_status >= StorageLimits.OVER_PRIVATE:
return {
'public': False,
'storage_limit_status': 'over'
}

@property
def event_type(self):
"""Most basic event type."""
Expand All @@ -83,6 +111,18 @@ def url(self):

return self._url.url

def perform(self):
emails.notify(
event=self.event_type,
user=self.user,
node=self.node,
timestamp=self.timestamp,
message=self.html_message,
profile_image_url=self.profile_image_url,
url=self.url,
storage_limit_context=self.storage_limit_context
)


@register(NodeLog.FILE_ADDED)
class FileAdded(FileEvent):
Expand Down
17 changes: 17 additions & 0 deletions website/templates/emails/file_updated.html.mako
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,23 @@
<span class="content" style="display: block;padding: 6px 5px 0px 8px;font-size: 14px;">
<span class="person" style="font-weight: bold;">${user.fullname} </span>
${message}
% if storage_limit_context and admin_recipient:
% if storage_limit_context['public']:
This public project is ${storage_limit_context['storage_limit_status']} the 50GB OSF Storage limit and requires your attention. In order to avoid disruption of your workflow, please take action through one of the following options:<br>
<ul>
<li>Connect an <a href="https://help.osf.io/hc/en-us/sections/360003623833-Storage-add-ons">OSF Storage add-on</a> to continue managing your research efficiently from OSF. OSF add-ons are an easy way to extend your storage space while also streamlining your data management workflow.</li>
<li><a href="https://help.osf.io/hc/en-us/articles/360019737614-Create-Components">Organize your project with components</a> to take advantage of the flexible structure and maximize storage options.</li>
</ul>
% else:
This private project is ${storage_limit_context['storage_limit_status']} the 5GB OSF Storage limit and requires your attention. In order to avoid disruption of your workflow, please take action through one of the following options:<br>
<ul>
<li>Connect an <a href="https://help.osf.io/hc/en-us/sections/360003623833-Storage-add-ons">OSF Storage add-on</a> to continue managing your research efficiently from OSF. OSF add-ons are an easy way to extend your storage space while also streamlining your data management workflow.</li>
<li>Free up space by making one or more of your components public. <a href="https://help.osf.io/hc/en-us/articles/360019737614-Create-Components">Organize your project with components</a> to take advantage of the flexible structure and maximize storage options.</li>
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the "organize your project" bit be its own list entry?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should be combined! Here's the doc I adapted the language from (https://docs.google.com/document/d/1gB5Ya4InmZ3ozOQND1E-drVxnwYJZmzlfV1ohqwr-kQ/edit#bookmark=id.oty6uwnocayh); there are some modifications because we decided to include the storage messaging in file notifications.

<li><a href="https://help.osf.io/hc/en-us/articles/360018981414-Control-Your-Privacy-Settings#Make-your-project-or-components-public">Make your project public</a> to increase storage capacity to 50 GB for files stored in OSF Storage.</li>
</ul>
% endif
Learn more about OSF Storage capacity limits <a href="https://help.osf.io/hc/en-us/articles/360054528874-OSF-Storage-Caps">here</a>.
% endif
</span>
</td>
<td width="25" style="text-align:center;border-collapse: collapse;font-size: 24px;border-left: 1px solid #ddd;">
Expand Down