Skip to content

Commit f870c19

Browse files
authored
Merge pull request #11310 from Ostap-Zherebetskyi/fix/file_event_notifications
[ENG-8861] File Operations: Email content and subject not correct when file is renamed
2 parents c57313a + 9c232e5 commit f870c19

File tree

8 files changed

+45
-81
lines changed

8 files changed

+45
-81
lines changed

addons/base/views.py

Lines changed: 2 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -611,48 +611,18 @@ def create_waterbutler_log(payload, **kwargs):
611611
target_node = AbstractNode.load(metadata.get('nid'))
612612
if target_node and payload['action'] != 'download_file':
613613
update_storage_usage_with_size(payload)
614-
615614
with transaction.atomic():
616-
file_signals.file_updated.send(target=node, user=user, payload=payload)
615+
file_signals.file_updated.send(target=node, user=user, event_type=action, payload=payload)
617616
return {'status': 'success'}
618617

619618

620619
@file_signals.file_updated.connect
621-
def emit_notification(self, target, user, payload, *args, **kwargs):
622-
if not isinstance(target, Preprint):
623-
return
624-
notification_types = {
625-
'rename': NotificationType.Type.ADDON_FILE_RENAMED.instance,
626-
'copy': NotificationType.Type.ADDON_FILE_COPIED.instance,
627-
'create': NotificationType.Type.FILE_ADDED.instance,
628-
'move': NotificationType.Type.ADDON_FILE_MOVED.instance,
629-
'delete': NotificationType.Type.FILE_REMOVED.instance,
630-
'update': NotificationType.Type.FILE_UPDATED.instance,
631-
'create_folder': NotificationType.Type.FOLDER_CREATED.instance,
632-
}
633-
notification_type = notification_types[payload.get('action')]
634-
if notification_type not in notification_types.values():
635-
raise NotImplementedError(f'Notification type {notification_type} is not supported')
636-
notification_type.emit(
637-
user=user,
638-
subscribed_object=target,
639-
event_context={
640-
'profile_image_url': user.profile_image_url(),
641-
'localized_timestamp': str(timezone.now()),
642-
'message': payload.get('message'),
643-
'user_fullname': user.fullname,
644-
'url': target.absolute_url,
645-
}
646-
)
647-
648-
@file_signals.file_updated.connect
649-
def addon_delete_file_node(self, target, user, payload):
620+
def addon_delete_file_node(self, target, user, event_type, payload):
650621
""" Get addon BaseFileNode(s), move it into the TrashedFileNode collection
651622
and remove it from StoredFileNode.
652623
Required so that the guids of deleted addon files are not re-pointed when an
653624
addon file or folder is moved or renamed.
654625
"""
655-
event_type = payload['action']
656626
if event_type == 'file_removed' and payload.get('provider', None) != 'osfstorage':
657627
provider = payload['provider']
658628
path = payload['metadata']['path']

notifications/file_event_notifications.py

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,8 @@ def perform(self):
6262
'url': self.url,
6363
'message': self.html_message,
6464
'localized_timestamp': str(self.timestamp),
65-
}
65+
},
66+
is_digest=True,
6667
)
6768

6869
@property
@@ -88,20 +89,20 @@ def event_type(self):
8889
# -----------------------------
8990

9091
@signal.connect
91-
def file_updated(self, target=None, user=None, payload=None):
92+
def file_updated(self, target=None, user=None, event_type=None, payload=None):
9293
"""Signal receiver for addon file updates."""
9394
if isinstance(target, Preprint):
9495
return
9596

9697
event = {
97-
'rename': NotificationType.Type.ADDON_FILE_RENAMED,
98-
'copy': NotificationType.Type.ADDON_FILE_COPIED,
99-
'create': NotificationType.Type.FILE_UPDATED,
100-
'move': NotificationType.Type.ADDON_FILE_MOVED,
101-
'delete': NotificationType.Type.FILE_REMOVED,
102-
'update': NotificationType.Type.FILE_UPDATED,
103-
'create_folder': NotificationType.Type.FILE_UPDATED,
104-
}[payload.get('action')]
98+
NodeLog.FILE_RENAMED: NotificationType.Type.ADDON_FILE_RENAMED,
99+
NodeLog.FILE_COPIED: NotificationType.Type.ADDON_FILE_COPIED,
100+
NodeLog.FILE_ADDED: NotificationType.Type.FILE_ADDED,
101+
NodeLog.FILE_MOVED: NotificationType.Type.ADDON_FILE_MOVED,
102+
NodeLog.FILE_REMOVED: NotificationType.Type.FILE_REMOVED,
103+
NodeLog.FILE_UPDATED: NotificationType.Type.FILE_UPDATED,
104+
NodeLog.FOLDER_CREATED: NotificationType.Type.FOLDER_CREATED,
105+
}[event_type]
105106

106107
if event not in event_registry:
107108
raise NotImplementedError(f'{event} not in {event_registry}')
@@ -262,10 +263,6 @@ def source_url(self):
262263
@register(NodeLog.FILE_RENAMED)
263264
class AddonFileRenamed(ComplexFileEvent):
264265
def perform(self):
265-
'''
266-
Currently, WB sends the "move" action for renamed files.
267-
This code will remain useless until the correct action is sent.
268-
'''
269266
if self.node == self.source_node:
270267
super().perform()
271268
return

notifications/tasks.py

Lines changed: 18 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ def send_user_email_task(self, user_id, notification_ids, message_freq):
2828
)
2929
except OSFUser.DoesNotExist:
3030
logger.error(f'OSFUser with id {user_id} does not exist')
31-
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id, status='NO_USER_FOUND')
31+
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id)
32+
email_task.status = 'NO_USER_FOUND'
3233
email_task.error_message = 'User not found or disabled'
3334
email_task.save()
3435
return
3536

3637
try:
37-
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id, user=user, status='STARTED')
38+
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id)
39+
email_task.user = user
40+
email_task.status = 'STARTED'
3841
if user.is_disabled:
3942
email_task.status = 'USER_DISABLED'
4043
email_task.error_message = 'User not found or disabled'
@@ -58,7 +61,6 @@ def send_user_email_task(self, user_id, notification_ids, message_freq):
5861
NotificationType.Type.USER_DIGEST.instance.emit(
5962
user=user,
6063
event_context=event_context,
61-
is_digest=True
6264
)
6365

6466
notifications_qs.update(sent=timezone.now())
@@ -73,11 +75,13 @@ def send_user_email_task(self, user_id, notification_ids, message_freq):
7375
)
7476
except OSFUser.DoesNotExist:
7577
logger.error(f'OSFUser with id {user_id} does not exist')
76-
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id, status='NO_USER_FOUND')
78+
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id)
79+
email_task.status = 'NO_USER_FOUND'
7780
email_task.error_message = 'User not found or disabled'
7881
email_task.save()
7982
return
80-
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id, user=user, status='RETRY')
83+
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id)
84+
email_task.user = user
8185
email_task.status = 'RETRY'
8286
email_task.error_message = str(e)
8387
email_task.save()
@@ -93,13 +97,16 @@ def send_moderator_email_task(self, user_id, provider_id, notification_ids, mess
9397
)
9498
except OSFUser.DoesNotExist:
9599
logger.error(f'OSFUser with id {user_id} does not exist')
96-
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id, status='NO_USER_FOUND')
100+
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id)
101+
email_task.status = 'NO_USER_FOUND'
97102
email_task.error_message = 'User not found or disabled'
98103
email_task.save()
99104
return
100105

101106
try:
102-
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id, user=user, status='STARTED')
107+
email_task, _ = EmailTask.objects.get_or_create(task_id=self.request.id)
108+
email_task.user = user
109+
email_task.status = 'STARTED'
103110
if user.is_disabled:
104111
email_task.status = 'USER_DISABLED'
105112
email_task.error_message = 'User not found or disabled'
@@ -173,7 +180,7 @@ def send_moderator_email_task(self, user_id, provider_id, notification_ids, mess
173180
logger.exception('Retrying send_moderator_email_task due to exception')
174181
raise self.retry(exc=e)
175182

176-
@celery_app.task(bind=True, name='notifications.tasks.send_users_digest_email')
183+
@celery_app.task(name='notifications.tasks.send_users_digest_email')
177184
def send_users_digest_email(dry_run=False):
178185
today = date.today()
179186

@@ -191,7 +198,7 @@ def send_users_digest_email(dry_run=False):
191198
if not dry_run:
192199
send_user_email_task.delay(user_id, notification_ids, freq)
193200

194-
@celery_app.task(bind=True, name='notifications.tasks.send_moderators_digest_email')
201+
@celery_app.task(name='notifications.tasks.send_moderators_digest_email')
195202
def send_moderators_digest_email(dry_run=False):
196203
today = date.today()
197204

@@ -315,7 +322,7 @@ def remove_subscription_task(node_id):
315322
).delete()
316323

317324

318-
@celery_app.task(bind=True, name='notifications.tasks.send_users_instant_digest_email')
325+
@celery_app.task(name='notifications.tasks.send_users_instant_digest_email')
319326
def send_users_instant_digest_email(dry_run):
320327
"""Send pending "instant' digest emails.
321328
:return:
@@ -327,7 +334,7 @@ def send_users_instant_digest_email(dry_run):
327334
if not dry_run:
328335
send_user_email_task.delay(user_id, notification_ids, 'instantly')
329336

330-
@celery_app.task(bind=True, name='notifications.tasks.send_moderators_instant_digest_email')
337+
@celery_app.task(name='notifications.tasks.send_moderators_instant_digest_email')
331338
def send_moderators_instant_digest_email(dry_run=False):
332339
"""Send pending "instant' digest emails.
333340
:return:

osf/models/notification.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -70,10 +70,10 @@ def mark_seen(self) -> None:
7070

7171
def render(self) -> str:
7272
"""Render the notification message using the event context."""
73-
template = self.subscription.notification_type.template
74-
if not template:
73+
notification_type = self.subscription.notification_type
74+
if not notification_type:
7575
raise ValueError('Notification type must have a template to render the notification.')
76-
notification = email.render_notification(template, self.event_context)
76+
notification = email._render_email_html(notification_type, self.event_context)
7777
return notification
7878

7979
def __str__(self) -> str:

tests/test_addons.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1569,7 +1569,7 @@ def test_delete_action_creates_trashed_file_node(self):
15691569
'materialized': '/test/Test'
15701570
}
15711571
}
1572-
views.addon_delete_file_node(self=None, target=self.project, user=self.user, payload=payload)
1572+
views.addon_delete_file_node(self=None, target=self.project, user=self.user, event_type='file_removed', payload=payload)
15731573
assert not GithubFileNode.load(file_node._id)
15741574
assert TrashedFileNode.load(file_node._id)
15751575

@@ -1590,7 +1590,7 @@ def test_delete_action_for_folder_deletes_subfolders_and_creates_trashed_file_no
15901590
'materialized': '/test/'
15911591
}
15921592
}
1593-
views.addon_delete_file_node(self=None, target=self.project, user=self.user, payload=payload)
1593+
views.addon_delete_file_node(self=None, target=self.project, user=self.user, event_type='file_removed', payload=payload)
15941594
assert not GithubFileNode.load(subfolder._id)
15951595
assert TrashedFileNode.load(file_node._id)
15961596

tests/test_preprints.py

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2174,8 +2174,7 @@ def test_add_log(self):
21742174
url = self.preprint.api_url_for('create_waterbutler_log')
21752175
payload = self.build_payload(metadata={'nid': self.preprint._id, 'materialized': path, 'kind': 'file', 'path': path})
21762176
nlogs = self.preprint.logs.count()
2177-
with capture_notifications():
2178-
self.app.put(url, json=payload)
2177+
self.app.put(url, json=payload)
21792178
self.preprint.reload()
21802179
assert self.preprint.logs.count() == nlogs + 1
21812180

@@ -2244,14 +2243,11 @@ def test_action_file_rename(self):
22442243
'kind': 'file',
22452244
},
22462245
)
2247-
with capture_notifications() as notifications:
2248-
self.app.put(
2249-
url,
2250-
json=payload,
2251-
)
2246+
self.app.put(
2247+
url,
2248+
json=payload,
2249+
)
22522250
self.preprint.reload()
2253-
assert len(notifications['emits']) == 1
2254-
assert notifications['emits'][0]['type'] == NotificationType.Type.ADDON_FILE_RENAMED
22552251
assert self.preprint.logs.latest().action == 'osf_storage_addon_file_renamed'
22562252

22572253
def test_action_downloads_contrib(self):
@@ -2279,11 +2275,8 @@ def test_add_file_osfstorage_log(self):
22792275
url = self.preprint.api_url_for('create_waterbutler_log')
22802276
payload = self.build_payload(metadata={'nid': self.preprint._id, 'materialized': path, 'kind': 'file', 'path': path})
22812277
nlogs = self.preprint.logs.count()
2282-
with capture_notifications() as notifications:
2283-
self.app.put(url, json=payload)
2278+
self.app.put(url, json=payload)
22842279
self.preprint.reload()
2285-
assert len(notifications['emits']) == 1
2286-
assert notifications['emits'][0]['type'] == NotificationType.Type.FILE_ADDED
22872280
assert self.preprint.logs.count() == nlogs + 1
22882281
assert ('urls' in self.preprint.logs.filter(action='osf_storage_file_added')[0].params)
22892282

@@ -2292,11 +2285,8 @@ def test_add_folder_osfstorage_log(self):
22922285
url = self.preprint.api_url_for('create_waterbutler_log')
22932286
payload = self.build_payload(metadata={'nid': self.preprint._id, 'materialized': path, 'kind': 'folder', 'path': path})
22942287
nlogs = self.preprint.logs.count()
2295-
with capture_notifications() as notifications:
2296-
self.app.put(url, json=payload)
2288+
self.app.put(url, json=payload)
22972289
self.preprint.reload()
2298-
assert len(notifications['emits']) == 1
2299-
assert notifications['emits'][0]['type'] == NotificationType.Type.FILE_ADDED
23002290
assert self.preprint.logs.count() == nlogs + 1
23012291
assert ('urls' not in self.preprint.logs.filter(action='osf_storage_file_added')[0].params)
23022292

website/project/views/comment.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414

1515

1616
@file_updated.connect
17-
def update_file_guid_referent(self, target, payload, user=None):
17+
def update_file_guid_referent(self, target, event_type, payload, user=None):
1818
event_type = payload['action']
1919
if event_type not in ('addon_file_moved', 'addon_file_renamed'):
2020
return # Nothing to do

website/settings/defaults.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -623,7 +623,7 @@ class CeleryConfig:
623623
'kwargs': {'dry_run': False},
624624
},
625625
'5-minute-user-emails': {
626-
'task': 'notifications.tasks.send_moderators_instant_digest_email',
626+
'task': 'notifications.tasks.send_users_instant_digest_email',
627627
'schedule': crontab(minute='*/5'),
628628
'kwargs': {'dry_run': False},
629629
},

0 commit comments

Comments
 (0)