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

ci: merge main to release #8344

Merged
merged 9 commits into from
Dec 18, 2024
14 changes: 13 additions & 1 deletion client/agenda/AgendaScheduleList.vue
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@ const meetingEvents = computed(() => {
const purposesWithoutLinks = ['admin', 'closed_meeting', 'officehours', 'social']
if (item.flags.showAgenda || (typesWithLinks.includes(item.type) && !purposesWithoutLinks.includes(item.purpose))) {
if (item.flags.agenda) {
// -> Meeting Materials
links.push({
id: `lnk-${item.id}-tar`,
label: 'Download meeting materials as .tar archive',
Expand All @@ -305,7 +306,18 @@ const meetingEvents = computed(() => {
color: 'red'
})
}
if (agendaStore.usesNotes) {
// -> Point to Wiki for Hackathon sessions, HedgeDocs otherwise
if (item.name.toLowerCase().includes('hackathon')) {
links.push({
id: `lnk-${item.id}-wiki`,
label: 'Wiki',
icon: 'book',
href: getUrl('hackathonWiki', {
meetingNumber: agendaStore.meeting.number
}),
color: 'blue'
})
} else if (agendaStore.usesNotes) {
links.push({
id: `lnk-${item.id}-note`,
label: 'Notepad for note-takers',
Expand Down
1 change: 1 addition & 0 deletions client/shared/urls.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"bofDefinition": "https://www.ietf.org/how/bofs/",
"hackathonWiki": "https://wiki.ietf.org/meeting/{meetingNumber}/hackathon",
"meetingCalIcs": "/meeting/{meetingNumber}/agenda.ics",
"meetingDetails": "/meeting/{meetingNumber}/session/{eventAcronym}/",
"meetingMaterialsPdf": "/meeting/{meetingNumber}/agenda/{eventAcronym}-drafts.pdf",
Expand Down
14 changes: 7 additions & 7 deletions dev/deploy-to-container/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion dev/deploy-to-container/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"dependencies": {
"dockerode": "^4.0.2",
"fs-extra": "^11.2.0",
"nanoid": "5.0.8",
"nanoid": "5.0.9",
"nanoid-dictionary": "5.0.0-beta.1",
"slugify": "1.6.6",
"tar": "^7.4.3",
Expand Down
12 changes: 12 additions & 0 deletions ietf/community/apps.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Copyright The IETF Trust 2024, All Rights Reserved

from django.apps import AppConfig


class CommunityConfig(AppConfig):
name = "ietf.community"

def ready(self):
"""Initialize the app after the registry is populated"""
# implicitly connects @receiver-decorated signals
from . import signals # pyflakes: ignore
35 changes: 2 additions & 33 deletions ietf/community/models.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,14 @@
# Copyright The IETF Trust 2012-2020, All Rights Reserved
# -*- coding: utf-8 -*-


from django.conf import settings
from django.db import models, transaction
from django.db.models import signals
from django.db import models
from django.urls import reverse as urlreverse

from ietf.doc.models import Document, DocEvent, State
from ietf.doc.models import Document, State
from ietf.group.models import Group
from ietf.person.models import Person, Email
from ietf.utils.models import ForeignKey

from .tasks import notify_event_to_subscribers_task


class CommunityList(models.Model):
person = ForeignKey(Person, blank=True, null=True)
Expand Down Expand Up @@ -98,29 +93,3 @@ class EmailSubscription(models.Model):

def __str__(self):
return "%s to %s (%s changes)" % (self.email, self.community_list, self.notify_on)


def notify_events(sender, instance, **kwargs):
if not isinstance(instance, DocEvent):
return

if not kwargs.get("created", False):
return # only notify on creation

if instance.doc.type_id != 'draft':
return

if getattr(instance, "skip_community_list_notification", False):
return

# kludge alert: queuing a celery task in response to a signal can cause unexpected attempts to
# start a Celery task during tests. To prevent this, don't queue a celery task if we're running
# tests.
if settings.SERVER_MODE != "test":
# Wrap in on_commit in case a transaction is open
transaction.on_commit(
lambda: notify_event_to_subscribers_task.delay(event_id=instance.pk)
)


signals.post_save.connect(notify_events)
44 changes: 44 additions & 0 deletions ietf/community/signals.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
# Copyright The IETF Trust 2024, All Rights Reserved

from django.conf import settings
from django.db import transaction
from django.db.models.signals import post_save
from django.dispatch import receiver

from ietf.doc.models import DocEvent
from .tasks import notify_event_to_subscribers_task


def notify_of_event(event: DocEvent):
"""Send subscriber notification emails for a 'draft'-related DocEvent

If the event is attached to a draft of type 'doc', queues a task to send notification emails to
community list subscribers. No emails will be sent when SERVER_MODE is 'test'.
"""
if event.doc.type_id != "draft":
return

if getattr(event, "skip_community_list_notification", False):
return

# kludge alert: queuing a celery task in response to a signal can cause unexpected attempts to
# start a Celery task during tests. To prevent this, don't queue a celery task if we're running
# tests.
if settings.SERVER_MODE != "test":
# Wrap in on_commit in case a transaction is open
transaction.on_commit(
lambda: notify_event_to_subscribers_task.delay(event_id=event.pk)
)


# dispatch_uid ensures only a single signal receiver binding is made
@receiver(post_save, dispatch_uid="notify_of_events_receiver_uid")
def notify_of_events_receiver(sender, instance, **kwargs):
"""Call notify_of_event after saving a new DocEvent"""
if not isinstance(instance, DocEvent):
return

if not kwargs.get("created", False):
return # only notify on creation

notify_of_event(instance)
67 changes: 36 additions & 31 deletions ietf/community/tests.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# Copyright The IETF Trust 2016-2023, All Rights Reserved
# -*- coding: utf-8 -*-


import mock
from pyquery import PyQuery

Expand All @@ -11,6 +10,7 @@
import debug # pyflakes:ignore

from ietf.community.models import CommunityList, SearchRule, EmailSubscription
from ietf.community.signals import notify_of_event
from ietf.community.utils import docs_matching_community_list_rule, community_list_rules_matching_doc
from ietf.community.utils import reset_name_contains_index_for_rule, notify_event_to_subscribers
from ietf.community.tasks import notify_event_to_subscribers_task
Expand Down Expand Up @@ -431,53 +431,58 @@ def test_subscription_for_group(self):
r = self.client.get(url)
self.assertEqual(r.status_code, 200)

# Mock out the on_commit call so we can tell whether the task was actually queued
@mock.patch("ietf.submit.views.transaction.on_commit", side_effect=lambda x: x())
@mock.patch("ietf.community.models.notify_event_to_subscribers_task")
def test_notification_signal_receiver(self, mock_notify_task, mock_on_commit):
"""Saving a DocEvent should notify subscribers
@mock.patch("ietf.community.signals.notify_of_event")
def test_notification_signal_receiver(self, mock_notify_of_event):
"""Saving a newly created DocEvent should notify subscribers

This implicitly tests that notify_events is hooked up to the post_save signal.
This implicitly tests that notify_of_event_receiver is hooked up to the post_save signal.
"""
# Arbitrary model that's not a DocEvent
person = PersonFactory()
mock_notify_task.reset_mock() # clear any calls that resulted from the factories
# be careful overriding SERVER_MODE - we do it here because the method
# under test does not make this call when in "test" mode
with override_settings(SERVER_MODE="not-test"):
person.save()
self.assertFalse(mock_notify_task.delay.called)

person = PersonFactory.build() # builds but does not save...
mock_notify_of_event.reset_mock() # clear any calls that resulted from the factories
person.save()
self.assertFalse(mock_notify_of_event.called)

# build a DocEvent that is not yet persisted
doc = DocumentFactory()
d = DocEventFactory.build(by=person, doc=doc)
# mock_notify_task.reset_mock() # clear any calls that resulted from the factories
event = DocEventFactory.build(by=person, doc=doc) # builds but does not save...
mock_notify_of_event.reset_mock() # clear any calls that resulted from the factories
event.save()
self.assertEqual(mock_notify_of_event.call_count, 1, "notify_task should be run on creation of DocEvent")
self.assertEqual(mock_notify_of_event.call_args, mock.call(event))

# save the existing DocEvent and see that no notification is sent
mock_notify_of_event.reset_mock()
event.save()
self.assertFalse(mock_notify_of_event.called, "notify_task should not be run save of on existing DocEvent")

# Mock out the on_commit call so we can tell whether the task was actually queued
@mock.patch("ietf.submit.views.transaction.on_commit", side_effect=lambda x: x())
@mock.patch("ietf.community.signals.notify_event_to_subscribers_task")
def test_notify_of_event(self, mock_notify_task, mock_on_commit):
"""The community notification task should be called as intended"""
person = PersonFactory() # builds but does not save...
doc = DocumentFactory()
event = DocEventFactory(by=person, doc=doc)
# be careful overriding SERVER_MODE - we do it here because the method
# under test does not make this call when in "test" mode
with override_settings(SERVER_MODE="not-test"):
d.save()
self.assertEqual(mock_notify_task.delay.call_count, 1, "notify_task should be run on creation of DocEvent")
self.assertEqual(mock_notify_task.delay.call_args, mock.call(event_id = d.pk))

mock_notify_task.reset_mock()
with override_settings(SERVER_MODE="not-test"):
d.save()
self.assertFalse(mock_notify_task.delay.called, "notify_task should not be run save of on existing DocEvent")

notify_of_event(event)
self.assertTrue(mock_notify_task.delay.called, "notify_task should run for a DocEvent on a draft")
mock_notify_task.reset_mock()
d = DocEventFactory.build(by=person, doc=doc)
d.skip_community_list_notification = True

event.skip_community_list_notification = True
# be careful overriding SERVER_MODE - we do it here because the method
# under test does not make this call when in "test" mode
with override_settings(SERVER_MODE="not-test"):
d.save()
notify_of_event(event)
self.assertFalse(mock_notify_task.delay.called, "notify_task should not run when skip_community_list_notification is set")

d = DocEventFactory.build(by=person, doc=DocumentFactory(type_id="rfc"))
event = DocEventFactory.build(by=person, doc=DocumentFactory(type_id="rfc"))
# be careful overriding SERVER_MODE - we do it here because the method
# under test does not make this call when in "test" mode
with override_settings(SERVER_MODE="not-test"):
d.save()
notify_of_event(event)
self.assertFalse(mock_notify_task.delay.called, "notify_task should not run on a document with type 'rfc'")

@mock.patch("ietf.utils.mail.send_mail_text")
Expand Down
Loading
Loading