Skip to content

Commit 24140fa

Browse files
Notify secretariat when conflict review/status change doc enters an announcement pending state. Fixes ietf-tools#2962. Commit ready for merge.
- Legacy-Id: 18159
1 parent fff927b commit 24140fa

10 files changed

Lines changed: 262 additions & 4 deletions

File tree

ietf/doc/mails.py

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,36 @@ def email_ad_approved_doc(request, doc, text):
4646
dict(text=text,
4747
docname=doc.filename_with_rev()),
4848
bcc=bcc)
49-
49+
50+
def email_ad_approved_conflict_review(request, review, ok_to_publish):
51+
"""Email notification when AD approves a conflict review"""
52+
conflictdoc = review.relateddocument_set.get(relationship__slug='conflrev').target.document
53+
(to, cc) = gather_address_lists("ad_approved_conflict_review")
54+
frm = request.user.person.formatted_email()
55+
send_mail(request,
56+
to,
57+
frm,
58+
"Approved: %s" % review.title,
59+
"doc/conflict_review/ad_approval_pending_email.txt",
60+
dict(ok_to_publish=ok_to_publish,
61+
review=review,
62+
conflictdoc=conflictdoc),
63+
cc=cc)
64+
65+
def email_ad_approved_status_change(request, status_change, related_doc_info):
66+
"""Email notification when AD approves a status change"""
67+
(to, cc) = gather_address_lists("ad_approved_status_change")
68+
frm = request.user.person.formatted_email()
69+
send_mail(request,
70+
to,
71+
frm,
72+
"Approved: %s" % status_change.title,
73+
"doc/status_change/ad_approval_pending_email.txt",
74+
dict(
75+
related_doc_info=related_doc_info
76+
),
77+
cc=cc)
78+
5079
def email_stream_changed(request, doc, old_stream, new_stream, text=""):
5180
"""Email the change text to the notify group and to the stream chairs"""
5281
streams = []

ietf/doc/tests_conflict_review.py

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -306,13 +306,66 @@ def approve_test_helper(self,approve_type):
306306
else:
307307
self.assertIn( 'NOT be published', ''.join(wrap(get_payload_text(outbox[0]), 2**16)))
308308

309-
310309
def test_approve_reqnopub(self):
310+
"""Test secretariat approving a conf review FROM the appr-reqnopub-pend state"""
311311
self.approve_test_helper('appr-reqnopub')
312312

313313
def test_approve_noprob(self):
314+
"""Test secretariat approving a conf review FROM the appr-reqnopub-pend state"""
314315
self.approve_test_helper('appr-noprob')
315316

317+
def approval_pend_notice_test_helper(self, approve_type, role):
318+
"""Test notification email when review state changed to a -pend state
319+
320+
Sets up, clears outbox, and changes state. If notifications are sent,
321+
asserts basic properties common to all approve_types.
322+
323+
Caller should inspect outbox to access notifications if any.
324+
"""
325+
doc = Document.objects.get(name='conflict-review-imaginary-irtf-submission')
326+
url = urlreverse('ietf.doc.views_conflict_review.change_state',kwargs=dict(name=doc.name))
327+
328+
login_testing_unauthorized(self, role, url)
329+
empty_outbox()
330+
331+
# Issue the request
332+
pending_pk = str(State.objects.get(used=True,
333+
slug=approve_type+'-pend',
334+
type__slug='conflrev').pk)
335+
r = self.client.post(url,dict(review_state=pending_pk,comment='some comment or other'))
336+
337+
# Check the results
338+
self.assertEqual(r.status_code, 302)
339+
340+
# If we received a notification, check the common features for all approve_types
341+
if len(outbox) > 0:
342+
notification = outbox[0]
343+
self.assertIn(doc.title, notification['Subject'])
344+
self.assertIn('iesg-secretary@ietf.org', notification['To'])
345+
self.assertTrue(notification['Subject'].startswith('Approved:'))
346+
347+
def test_approval_pend_notice_ad_reqnopub(self):
348+
"""Test notification email when AD puts doc in appr-reqnopub-pend state"""
349+
self.approval_pend_notice_test_helper('appr-reqnopub', 'ad')
350+
self.assertEqual(len(outbox), 1)
351+
self.assertIn('NOT be published', get_payload_text(outbox[0]))
352+
353+
def test_no_approval_pend_notice_secr_reqnopub(self):
354+
"""Test notification email when secretariat puts doc in appr-reqnopub-pend state"""
355+
self.approval_pend_notice_test_helper('appr-reqnopub', 'secretariat')
356+
self.assertEqual(len(outbox), 0) # no notification should be sent
357+
358+
def test_approval_pend_notice_ad_noprob(self):
359+
"""Test notification email when AD puts doc in appr-noprob-pend state"""
360+
self.approval_pend_notice_test_helper('appr-noprob', 'ad')
361+
self.assertEqual(len(outbox), 1)
362+
self.assertIn('IESG has no problem', get_payload_text(outbox[0]))
363+
364+
def test_no_approval_pend_notice_secr_noprob(self):
365+
"""Test notification email when secretariat puts doc in appr-noprob-pend state"""
366+
self.approval_pend_notice_test_helper('appr-noprob', 'secretariat')
367+
self.assertEqual(len(outbox), 0)
368+
316369
def setUp(self):
317370
IndividualDraftFactory(name='draft-imaginary-independent-submission')
318371
ConflictReviewFactory(name='conflict-review-imaginary-irtf-submission',review_of=IndividualDraftFactory(name='draft-imaginary-irtf-submission',stream_id='irtf'),notify='notifyme@example.net')

ietf/doc/tests_status_change.py

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@
2323
from ietf.group.models import Person
2424
from ietf.iesg.models import TelechatDate
2525
from ietf.utils.test_utils import TestCase
26-
from ietf.utils.mail import outbox
26+
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
2727
from ietf.utils.test_utils import login_testing_unauthorized
2828

2929

@@ -335,6 +335,52 @@ def test_approve(self):
335335

336336
self.assertTrue(doc.latest_event(DocEvent,type="added_comment").desc.startswith('The following approval message was sent'))
337337

338+
def approval_pend_notice_test_helper(self, role):
339+
"""Test notification email when review state changed to the appr-pend state"""
340+
doc = Document.objects.get(name='status-change-imaginary-mid-review')
341+
url = urlreverse('ietf.doc.views_status_change.change_state',kwargs=dict(name=doc.name))
342+
343+
# Add some status change related documents
344+
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9999'),relationship_id='tois')
345+
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc9998'),relationship_id='tohist')
346+
# And a non-status change related document
347+
doc.relateddocument_set.create(target=DocAlias.objects.get(name='rfc14'),relationship_id='updates')
348+
349+
login_testing_unauthorized(self, role, url)
350+
empty_outbox()
351+
352+
# Issue the request
353+
appr_pend_pk = str(State.objects.get(used=True,
354+
slug='appr-pend',
355+
type__slug='statchg').pk)
356+
r = self.client.post(url,dict(new_state=appr_pend_pk,comment='some comment or other'))
357+
358+
# Check the results
359+
self.assertEqual(r.status_code, 302)
360+
361+
if role == 'ad':
362+
self.assertEqual(len(outbox), 1)
363+
notification = outbox[0]
364+
self.assertIn(doc.title, notification['Subject'])
365+
self.assertIn('iesg-secretary@ietf.org', notification['To'])
366+
self.assertTrue(notification['Subject'].startswith('Approved:'))
367+
notification_text = get_payload_text(notification)
368+
self.assertIn('The AD has approved changing the status', notification_text)
369+
self.assertIn(DocAlias.objects.get(name='rfc9999').document.canonical_name(), notification_text)
370+
self.assertIn(DocAlias.objects.get(name='rfc9998').document.canonical_name(), notification_text)
371+
self.assertNotIn(DocAlias.objects.get(name='rfc14').document.canonical_name(), notification_text)
372+
self.assertNotIn('No value found for', notification_text) # make sure all interpolation values were set
373+
else:
374+
self.assertEqual(len(outbox), 0)
375+
376+
def test_approval_pend_notice_ad(self):
377+
"""Test that an approval notice is sent to secretariat when AD approves status change"""
378+
self.approval_pend_notice_test_helper('ad')
379+
380+
def test_no_approval_pend_notice_secr(self):
381+
"""Test that no approval notice is sent when secretariat approves status change"""
382+
self.approval_pend_notice_test_helper('secretariat')
383+
338384
def test_edit_relations(self):
339385
doc = Document.objects.get(name='status-change-imaginary-mid-review')
340386
url = urlreverse('ietf.doc.views_status_change.edit_relations',kwargs=dict(name=doc.name))

ietf/doc/views_conflict_review.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919
Document, NewRevisionDocEvent, State )
2020
from ietf.doc.utils import ( add_state_change_event, close_open_ballots,
2121
create_ballot_if_not_open, update_telechat )
22-
from ietf.doc.mails import email_iana
22+
from ietf.doc.mails import email_iana, email_ad_approved_conflict_review
2323
from ietf.doc.forms import AdForm
2424
from ietf.group.models import Role, Group
2525
from ietf.iesg.models import TelechatDate
@@ -79,6 +79,15 @@ def change_state(request, name, option=None):
7979
pos.save()
8080
# Consider mailing that position to 'iesg_ballot_saved'
8181
send_conflict_eval_email(request,review)
82+
elif (new_state.slug in ("appr-reqnopub-pend", "appr-noprob-pend")
83+
and has_role(request.user, "Area Director")):
84+
if new_state.slug == "appr-noprob-pend":
85+
ok_to_publish = True
86+
else:
87+
ok_to_publish = False
88+
email_ad_approved_conflict_review(request,
89+
review,
90+
ok_to_publish)
8291

8392

8493
return redirect('ietf.doc.views_doc.document_main', name=review.name)

ietf/doc/views_status_change.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
from django.utils.encoding import force_text
1919

2020
import debug # pyflakes:ignore
21+
from ietf.doc.mails import email_ad_approved_status_change
2122

2223
from ietf.doc.models import ( Document, DocAlias, State, DocEvent, BallotDocEvent,
2324
BallotPositionDocEvent, NewRevisionDocEvent, WriteupDocEvent, STATUSCHANGE_RELATIONS )
@@ -90,6 +91,21 @@ def change_state(request, name, option=None):
9091
dict(doc=status_change,
9192
url = status_change.get_absolute_url(),
9293
))
94+
elif new_state.slug == 'appr-pend' and has_role(request.user, "Area Director"):
95+
related_docs = status_change.relateddocument_set.filter(
96+
relationship__slug__in=STATUSCHANGE_RELATIONS
97+
)
98+
related_doc_info = [
99+
dict(title=rel_doc.target.document.title,
100+
canonical_name=rel_doc.target.document.canonical_name(),
101+
newstatus=newstatus(rel_doc))
102+
for rel_doc in related_docs
103+
]
104+
email_ad_approved_status_change(
105+
request,
106+
status_change,
107+
related_doc_info=related_doc_info,
108+
)
93109

94110
return redirect('ietf.doc.views_doc.document_main', name=status_change.name)
95111
else:
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright The IETF Trust 2020, All Rights Reserved
2+
3+
from django.db import migrations
4+
5+
6+
def forward(apps, schema_editor):
7+
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
8+
Recipient = apps.get_model('mailtrigger', 'Recipient')
9+
10+
ad_approved_conflict_review = MailTrigger.objects.create(
11+
slug='ad_approved_conflict_review',
12+
desc='Recipients when AD approves a conflict review pending announcement',
13+
)
14+
ad_approved_conflict_review.to.add(
15+
Recipient.objects.get(pk='iesg_secretary')
16+
)
17+
18+
19+
def reverse(apps, schema_editor):
20+
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
21+
MailTrigger.objects.filter(slug='ad_approved_conflict_review').delete()
22+
23+
24+
class Migration(migrations.Migration):
25+
dependencies = [
26+
('mailtrigger', '0013_add_irsg_ballot_saved'),
27+
]
28+
29+
operations = [
30+
migrations.RunPython(forward, reverse),
31+
]
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Copyright The IETF Trust 2020, All Rights Reserved
2+
3+
from django.db import migrations
4+
5+
6+
def forward(apps, schema_editor):
7+
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
8+
Recipient = apps.get_model('mailtrigger', 'Recipient')
9+
10+
ad_approved_conflict_review = MailTrigger.objects.create(
11+
slug='ad_approved_status_change',
12+
desc='Recipients when AD approves a status change pending announcement',
13+
)
14+
ad_approved_conflict_review.to.add(
15+
Recipient.objects.get(pk='iesg_secretary')
16+
)
17+
18+
19+
def reverse(apps, schema_editor):
20+
MailTrigger = apps.get_model('mailtrigger', 'MailTrigger')
21+
MailTrigger.objects.filter(slug='ad_approved_status_change').delete()
22+
23+
24+
class Migration(migrations.Migration):
25+
dependencies = [
26+
('mailtrigger', '0014_add_ad_approved_conflict_review'),
27+
]
28+
29+
operations = [
30+
migrations.RunPython(forward, reverse),
31+
]

ietf/name/fixtures/names.json

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3124,6 +3124,28 @@
31243124
"model": "group.groupfeatures",
31253125
"pk": "wg"
31263126
},
3127+
{
3128+
"fields": {
3129+
"cc": [],
3130+
"desc": "Recipients when AD approves a conflict review pending announcement",
3131+
"to": [
3132+
"iesg_secretary"
3133+
]
3134+
},
3135+
"model": "mailtrigger.mailtrigger",
3136+
"pk": "ad_approved_conflict_review"
3137+
},
3138+
{
3139+
"fields": {
3140+
"cc": [],
3141+
"desc": "Recipients when AD approves a status change pending announcement",
3142+
"to": [
3143+
"iesg_secretary"
3144+
]
3145+
},
3146+
"model": "mailtrigger.mailtrigger",
3147+
"pk": "ad_approved_status_change"
3148+
},
31273149
{
31283150
"fields": {
31293151
"cc": [
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
{% load ietf_filters %}{% load mail_filters %}{% autoescape off %}
2+
{% filter wordwrap:78 %}{{ review.title }} has been approved and the result is ready to announce.
3+
4+
{% if ok_to_publish %}
5+
The IESG has no problem with the publication of '{{ conflictdoc.title }}' {{ conflictdoc.file_tag|safe }} as {{ conflictdoc|std_level_prompt_with_article }}.
6+
{% else %}
7+
The IESG recommends that '{{ conflictdoc.title }}' {{ conflictdoc.file_tag|safe }} NOT be published as {{ conflictdoc|std_level_prompt_with_article }}.
8+
{% endif %}
9+
10+
{% endfilter %}
11+
12+
{% endautoescape %}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% load ietf_filters %}{% load mail_filters %}{% autoescape off %}
2+
{% filter wordwrap:78 %}The AD has approved changing the status of the following {% if related_doc_info|length == 1 %}document{% else %}documents{% endif %}:
3+
{% for relateddoc in related_doc_info %}- {{relateddoc.title }}
4+
({{relateddoc.canonical_name }}) to {{ relateddoc.newstatus }}
5+
{% endfor %}
6+
An announcement has not yet been sent.
7+
{% endfilter %}
8+
9+
{% endautoescape %}

0 commit comments

Comments
 (0)