Skip to content

Commit c40438f

Browse files
cslzchenOstap-Zherebetskyibodintsov
authored
[ENG-7812] Notification Refactor Phase 2 - Project PR (#11498)
Notification refactor project - phase 2 --------- Co-authored-by: Ostap Zherebetskyi <ozherebetskyi@exoft.net> Co-authored-by: Bohdan Odintsov <bodintsov@exoft.net> Co-authored-by: John Tordoff <>
1 parent f0f5ca9 commit c40438f

399 files changed

Lines changed: 15766 additions & 12570 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/test-build.yml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -201,3 +201,31 @@ jobs:
201201
- name: Upload report
202202
if: (github.event_name != 'pull_request') && (success() || failure()) # run this step even if previous step failed
203203
uses: ./.github/actions/gen-report
204+
205+
scripts:
206+
runs-on: ubuntu-22.04
207+
permissions:
208+
checks: write
209+
needs: build-cache
210+
services:
211+
postgres:
212+
image: postgres
213+
214+
env:
215+
POSTGRES_PASSWORD: ${{ env.OSF_DB_PASSWORD }}
216+
options: >-
217+
--health-cmd pg_isready
218+
--health-interval 10s
219+
--health-timeout 5s
220+
--health-retries 5
221+
ports:
222+
# Maps tcp port 5432 on service container to the host
223+
- 5432:5432
224+
steps:
225+
- uses: actions/checkout@v2
226+
- uses: ./.github/actions/start-build
227+
- name: Run tests
228+
run: poetry run python3 -m invoke test-ci-scripts -n 1 --junit
229+
- name: Upload report
230+
if: (github.event_name != 'pull_request') && (success() || failure()) # run this step even if previous step failed
231+
uses: ./.github/actions/gen-report

README-docker-compose.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -270,6 +270,12 @@
270270
```bash
271271
docker compose run --rm web python3 -m scripts.parse_citation_styles
272272
```
273+
- Populate Notification Types
274+
- Needed for notifications.
275+
```bash
276+
docker compose run --rm web python3 manage.py populate_notification_types
277+
```
278+
- _NOTE: The waffle switch `POPULATE_NOTIFICATION_TYPES` needs to be turned on.
273279
- Start ember_osf_web
274280
- Needed for ember app:
275281
- `docker-compose up -d ember_osf_web`

addons/base/views.py

Lines changed: 27 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,6 @@
3434
from framework.flask import redirect
3535
from framework.sentry import log_exception
3636
from framework.transactions.handlers import no_auto_transaction
37-
from website import mails
3837
from website import settings
3938
from addons.base import signals as file_signals
4039
from addons.base.utils import format_last_known_metadata, get_mfr_url
@@ -52,7 +51,7 @@
5251
DraftRegistration,
5352
Guid,
5453
FileVersionUserMetadata,
55-
FileVersion
54+
FileVersion, NotificationType
5655
)
5756
from osf.metrics import PreprintView, PreprintDownload
5857
from osf.utils import permissions
@@ -64,7 +63,7 @@
6463
from website.util import rubeus
6564

6665
# import so that associated listener is instantiated and gets emails
67-
from website.notifications.events.files import FileEvent # noqa
66+
from notifications.file_event_notifications import FileEvent # noqa
6867

6968
ERROR_MESSAGES = {'FILE_GONE': """
7069
<style>
@@ -226,8 +225,6 @@ def get_auth(auth, **kwargs):
226225
_check_resource_permissions(resource, auth, action)
227226

228227
provider_name = waterbutler_data['provider']
229-
waterbutler_settings = None
230-
waterbutler_credentials = None
231228
file_version = file_node = None
232229
if provider_name == 'osfstorage' or (not flag_is_active(request, features.ENABLE_GV)):
233230
file_version, file_node = _get_osfstorage_file_version_and_node(
@@ -576,20 +573,32 @@ def create_waterbutler_log(payload, **kwargs):
576573
params=payload
577574
)
578575

579-
if payload.get('email') is True or payload.get('errors'):
580-
mails.send_mail(
581-
user.username,
582-
mails.FILE_OPERATION_FAILED if payload.get('errors')
583-
else mails.FILE_OPERATION_SUCCESS,
584-
action=payload['action'],
585-
source_node=source_node,
586-
destination_node=destination_node,
587-
source_path=payload['source']['materialized'],
588-
source_addon=payload['source']['addon'],
589-
destination_addon=payload['destination']['addon'],
590-
osf_support_email=settings.OSF_SUPPORT_EMAIL
576+
if payload.get('email') or payload.get('errors'):
577+
if payload.get('email'):
578+
notification_type = NotificationType.Type.USER_FILE_OPERATION_SUCCESS.instance
579+
if payload.get('errors'):
580+
notification_type = NotificationType.Type.USER_FILE_OPERATION_FAILED.instance
581+
notification_type.emit(
582+
user=user,
583+
subscribed_object=node,
584+
event_context={
585+
'user_fullname': user.fullname,
586+
'action': payload['action'],
587+
'source_node': source_node._id,
588+
'source_node_title': source_node.title,
589+
'destination_node': destination_node._id,
590+
'destination_node_title': destination_node.title,
591+
'destination_node_parent_node_title': destination_node.parent_node.title if destination_node.parent_node else None,
592+
'source_path': payload['source']['materialized'],
593+
'source_addon': payload['source']['addon'],
594+
'destination_addon': payload['destination']['addon'],
595+
'osf_support_email': settings.OSF_SUPPORT_EMAIL,
596+
'logo': settings.OSF_LOGO,
597+
'OSF_LOGO_LIST': settings.OSF_LOGO_LIST,
598+
'OSF_LOGO': settings.OSF_LOGO,
599+
'domain': settings.DOMAIN,
600+
}
591601
)
592-
593602
if payload.get('errors'):
594603
# Action failed but our function succeeded
595604
# Bail out to avoid file_signals
@@ -603,10 +612,8 @@ def create_waterbutler_log(payload, **kwargs):
603612
target_node = AbstractNode.load(metadata.get('nid'))
604613
if target_node and payload['action'] != 'download_file':
605614
update_storage_usage_with_size(payload)
606-
607615
with transaction.atomic():
608616
file_signals.file_updated.send(target=node, user=user, event_type=action, payload=payload)
609-
610617
return {'status': 'success'}
611618

612619

addons/boa/tasks.py

Lines changed: 33 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,9 @@
1414
from addons.boa.boa_error_code import BoaErrorCode
1515
from framework import sentry
1616
from framework.celery_tasks import app as celery_app
17-
from osf.models import OSFUser
17+
from osf.models import OSFUser, NotificationType
1818
from osf.utils.fields import ensure_str, ensure_bytes
1919
from website import settings as osf_settings
20-
from website.mails import send_mail, ADDONS_BOA_JOB_COMPLETE, ADDONS_BOA_JOB_FAILURE
2120

2221
logger = logging.getLogger(__name__)
2322

@@ -184,18 +183,19 @@ async def submit_to_boa_async(host, username, password, user_guid, project_guid,
184183

185184
logger.info('Successfully uploaded query output to OSF.')
186185
logger.debug('Task ends <<<<<<<<')
187-
await sync_to_async(send_mail)(
188-
to_addr=user.username,
189-
mail=ADDONS_BOA_JOB_COMPLETE,
190-
fullname=user.fullname,
191-
query_file_name=query_file_name,
192-
query_file_full_path=file_full_path,
193-
output_file_name=output_file_name,
194-
job_id=boa_job.id,
195-
project_url=project_url,
196-
boa_job_list_url=boa_settings.BOA_JOB_LIST_URL,
197-
boa_support_email=boa_settings.BOA_SUPPORT_EMAIL,
198-
osf_support_email=osf_settings.OSF_SUPPORT_EMAIL,
186+
NotificationType.Type.ADDONS_BOA_JOB_COMPLETE.instance.emit(
187+
user=user,
188+
event_context={
189+
'user_fullname': user.fullname,
190+
'query_file_name': query_file_name,
191+
'query_file_full_path': file_full_path,
192+
'output_file_name': output_file_name,
193+
'job_id': boa_job.id,
194+
'project_url': project_url,
195+
'boa_job_list_url': boa_settings.BOA_JOB_LIST_URL,
196+
'boa_support_email': boa_settings.BOA_SUPPORT_EMAIL,
197+
'osf_support_email': osf_settings.OSF_SUPPORT_EMAIL,
198+
}
199199
)
200200
return BoaErrorCode.NO_ERROR
201201

@@ -209,22 +209,24 @@ def handle_boa_error(message, code, username, fullname, project_url, query_file_
209209
sentry.log_message(message, skip_session=True)
210210
except Exception:
211211
pass
212-
send_mail(
213-
to_addr=username,
214-
mail=ADDONS_BOA_JOB_FAILURE,
215-
fullname=fullname,
216-
code=code,
217-
message=message,
218-
query_file_name=query_file_name,
219-
file_size=file_size,
220-
max_file_size=boa_settings.MAX_SUBMISSION_SIZE,
221-
query_file_full_path=query_file_full_path,
222-
output_file_name=output_file_name,
223-
job_id=job_id,
224-
max_job_wait_hours=boa_settings.MAX_JOB_WAITING_TIME / 3600,
225-
project_url=project_url,
226-
boa_job_list_url=boa_settings.BOA_JOB_LIST_URL,
227-
boa_support_email=boa_settings.BOA_SUPPORT_EMAIL,
228-
osf_support_email=osf_settings.OSF_SUPPORT_EMAIL,
212+
NotificationType.Type.ADDONS_BOA_JOB_FAILURE.instance.emit(
213+
destination_address=username,
214+
event_context={
215+
'user_fullname': fullname,
216+
'code': code,
217+
'query_file_name': query_file_name,
218+
'file_size': file_size,
219+
'message': message,
220+
'max_file_size': boa_settings.MAX_SUBMISSION_SIZE,
221+
'query_file_full_path': query_file_full_path,
222+
'output_file_name': output_file_name,
223+
'job_id': job_id,
224+
'max_job_wait_hours': boa_settings.MAX_JOB_WAITING_TIME / 3600,
225+
'project_url': project_url,
226+
'boa_job_list_url': boa_settings.BOA_JOB_LIST_URL,
227+
'boa_support_email': boa_settings.BOA_SUPPORT_EMAIL,
228+
'osf_support_email': osf_settings.OSF_SUPPORT_EMAIL,
229+
230+
}
229231
)
230232
return code

addons/boa/tests/test_tasks.py

Lines changed: 21 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,10 +9,11 @@
99
from addons.boa import settings as boa_settings
1010
from addons.boa.boa_error_code import BoaErrorCode
1111
from addons.boa.tasks import submit_to_boa, submit_to_boa_async, handle_boa_error
12+
from osf.models import NotificationType
1213
from osf_tests.factories import AuthUserFactory, ProjectFactory
1314
from tests.base import OsfTestCase
15+
from tests.utils import capture_notifications
1416
from website import settings as osf_settings
15-
from website.mails import ADDONS_BOA_JOB_COMPLETE, ADDONS_BOA_JOB_FAILURE
1617

1718
DEFAULT_REFRESH_JOB_INTERVAL = boa_settings.REFRESH_JOB_INTERVAL
1819
DEFAULT_MAX_JOB_WAITING_TIME = boa_settings.MAX_JOB_WAITING_TIME
@@ -38,12 +39,6 @@ def setUp(self):
3839
self.output_file_name = 'fake_boa_script_results.txt'
3940
self.job_id = '1a2b3c4d5e6f7g8'
4041

41-
from conftest import start_mock_send_grid
42-
self.mock_send_grid = start_mock_send_grid(self)
43-
44-
def tearDown(self):
45-
super().tearDown()
46-
4742
def test_boa_error_code(self):
4843
assert BoaErrorCode.NO_ERROR == -1
4944
assert BoaErrorCode.UNKNOWN == 0
@@ -55,27 +50,26 @@ def test_boa_error_code(self):
5550
assert BoaErrorCode.FILE_TOO_LARGE_ERROR == 6
5651
assert BoaErrorCode.JOB_TIME_OUT_ERROR == 7
5752

58-
@mock.patch('website.mails.settings.USE_EMAIL', True)
59-
@mock.patch('website.mails.settings.USE_CELERY', False)
6053
def test_handle_boa_error(self):
61-
with mock.patch('addons.boa.tasks.sentry.log_message', return_value=None) as mock_sentry_log_message, \
62-
mock.patch('addons.boa.tasks.logger.error', return_value=None) as mock_logger_error:
63-
return_value = handle_boa_error(
64-
self.error_message,
65-
BoaErrorCode.UNKNOWN,
66-
self.user_username,
67-
self.user_fullname,
68-
self.project_url,
69-
self.file_full_path,
70-
query_file_name=self.query_file_name,
71-
file_size=self.file_size,
72-
output_file_name=self.output_file_name,
73-
job_id=self.job_id
74-
)
75-
self.mock_send_grid.assert_called()
76-
mock_sentry_log_message.assert_called_with(self.error_message, skip_session=True)
77-
mock_logger_error.assert_called_with(self.error_message)
78-
assert return_value == BoaErrorCode.UNKNOWN
54+
with mock.patch('addons.boa.tasks.sentry.log_message', return_value=None) as mock_sentry_log_message:
55+
with mock.patch('addons.boa.tasks.logger.error', return_value=None) as mock_logger_error:
56+
with capture_notifications() as notifications:
57+
return_value = handle_boa_error(
58+
self.error_message,
59+
BoaErrorCode.UNKNOWN,
60+
self.user_username,
61+
self.user_fullname,
62+
self.project_url,
63+
self.file_full_path,
64+
file_size=self.file_size,
65+
output_file_name=self.output_file_name,
66+
job_id=self.job_id
67+
)
68+
assert len(notifications['emits']) == 1
69+
assert notifications['emits'][0]['type'] == NotificationType.Type.ADDONS_BOA_JOB_FAILURE
70+
mock_sentry_log_message.assert_called_with(self.error_message, skip_session=True)
71+
mock_logger_error.assert_called_with(self.error_message)
72+
assert return_value == BoaErrorCode.UNKNOWN
7973

8074

8175
class TestSubmitToBoa(OsfTestCase):
@@ -154,14 +148,6 @@ def setUp(self):
154148
boa_settings.REFRESH_JOB_INTERVAL = DEFAULT_REFRESH_JOB_INTERVAL
155149
boa_settings.MAX_JOB_WAITING_TIME = DEFAULT_MAX_JOB_WAITING_TIME
156150

157-
from conftest import start_mock_send_grid
158-
self.mock_send_grid = start_mock_send_grid(self)
159-
160-
def tearDown(self):
161-
super().tearDown()
162-
163-
@mock.patch('website.mails.settings.USE_EMAIL', True)
164-
@mock.patch('website.mails.settings.USE_CELERY', False)
165151
async def test_submit_success(self):
166152
with mock.patch('osf.models.user.OSFUser.objects.get', return_value=self.user), \
167153
mock.patch('osf.models.user.OSFUser.get_or_create_cookie', return_value=self.user_cookie), \
@@ -190,7 +176,6 @@ async def test_submit_success(self):
190176
assert self.mock_job.refresh.call_count == 4
191177
assert mock_async_sleep.call_count == 4
192178
mock_close.assert_called()
193-
self.mock_send_grid.assert_called()
194179
mock_handle_boa_error.assert_not_called()
195180

196181
async def test_download_error(self):

addons/osfstorage/tests/test_models.py

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99

1010
from framework.auth import Auth
1111
from addons.osfstorage.models import OsfStorageFile, OsfStorageFileNode, OsfStorageFolder
12-
from osf.models import BaseFileNode
12+
from osf.models import BaseFileNode, NotificationType
1313
from osf.exceptions import ValidationError
1414
from osf.utils.permissions import WRITE, ADMIN
1515

@@ -24,6 +24,7 @@
2424
from osf import models
2525
from addons.osfstorage import utils
2626
from addons.osfstorage import settings
27+
from tests.utils import capture_notifications
2728
from website.files.exceptions import FileNodeCheckedOutError, FileNodeIsPrimaryFile
2829

2930
SessionStore = import_module(django_conf_settings.SESSION_ENGINE).SessionStore
@@ -745,7 +746,11 @@ def test_after_fork_copies_versions(self, node, node_settings, auth_obj):
745746
version = factories.FileVersionFactory()
746747
record.add_version(version)
747748

748-
fork = node.fork_node(auth_obj)
749+
with capture_notifications() as notifications:
750+
fork = node.fork_node(auth_obj)
751+
752+
assert len(notifications['emits']) == 1
753+
assert notifications['emits'][0]['type'] == NotificationType.Type.NODE_CONTRIBUTOR_ADDED_DEFAULT
749754
fork_node_settings = fork.get_addon('osfstorage')
750755
fork_node_settings.reload()
751756

@@ -757,7 +762,8 @@ def test_fork_reverts_to_node_storage_region(self, user2, region, region2, node,
757762
"""
758763
Despite different user regions defaults, the forked node always stay in the same region as it's original node.
759764
"""
760-
fork = node.fork_node(Auth(user2))
765+
with capture_notifications():
766+
fork = node.fork_node(Auth(user2))
761767
assert fork.get_addon('osfstorage').region_id == region.id
762768

763769
# don't inherit or override region

addons/wiki/tests/test_wiki.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
from framework.auth import Auth
3030
from django.utils import timezone
3131
from addons.wiki.utils import to_mongo_key
32+
from tests.utils import capture_notifications
3233

3334
from .config import EXAMPLE_DOCS, EXAMPLE_OPS
3435

@@ -818,7 +819,8 @@ def test_uuids_differ_between_forks(self):
818819
assert project_res.status_code == 200
819820
self.project.reload()
820821

821-
fork = self.project.fork_node(Auth(self.user))
822+
with capture_notifications():
823+
fork = self.project.fork_node(Auth(self.user))
822824
assert fork.is_fork_of(self.project)
823825
fork_url = fork.web_url_for('project_wiki_view', wname=self.wname)
824826
fork_res = self.app.get(fork_url, auth=self.user.auth)
@@ -1084,7 +1086,8 @@ def test_get_sharejs_uuid(self):
10841086
# Differs across projects and forks
10851087
project = ProjectFactory()
10861088
assert sharejs_uuid != get_sharejs_uuid(project, wname)
1087-
fork = self.project.fork_node(Auth(self.project.creator))
1089+
with capture_notifications():
1090+
fork = self.project.fork_node(Auth(self.project.creator))
10881091
assert sharejs_uuid != get_sharejs_uuid(fork, wname)
10891092

10901093
def test_generate_share_uuid(self):

0 commit comments

Comments
 (0)