Skip to content

Commit c36f63b

Browse files
authored
feat: Revamp AD dashboard (ietf-tools#6534)
* fix: Reorder conflict review columns Fixes ietf-tools#6528 Also remove some redundant computation while I'm here. * Remove some more stuff that isn't needed * Progress * Delivers current functionality * Add some comments * Handle expired docs * Interim commit * Fix tests * Cleanup * More cleanup * Reduce differences to current view * Interim commit * More progress * Getting close * Make page functional again * Remove unused variable * Suppress mypy warning * Fix ietf-tools#6553 * Log in as secretary to execute new code, and remove redundant check * Remove unneeded code * Fix ietf-tools#6608 by adding link to state description to state heading * Missed part of this change in last commit. Also fix an unrelated template nit while I'm here.
1 parent 5eb2f56 commit c36f63b

9 files changed

Lines changed: 494 additions & 420 deletions

File tree

ietf/doc/tests.py

Lines changed: 53 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -36,15 +36,14 @@
3636
from ietf.doc.models import ( Document, DocAlias, DocRelationshipName, RelatedDocument, State,
3737
DocEvent, BallotPositionDocEvent, LastCallDocEvent, WriteupDocEvent, NewRevisionDocEvent, BallotType,
3838
EditedAuthorsDocEvent )
39-
from ietf.doc.factories import ( DocumentFactory, DocEventFactory, CharterFactory,
39+
from ietf.doc.factories import ( DocumentFactory, DocEventFactory, CharterFactory,
4040
ConflictReviewFactory, WgDraftFactory, IndividualDraftFactory, WgRfcFactory,
4141
IndividualRfcFactory, StateDocEventFactory, BallotPositionDocEventFactory,
4242
BallotDocEventFactory, DocumentAuthorFactory, NewRevisionDocEventFactory,
43-
StatusChangeFactory, BofreqFactory, DocExtResourceFactory, RgDraftFactory)
43+
StatusChangeFactory, DocExtResourceFactory, RgDraftFactory)
4444
from ietf.doc.forms import NotifyForm
4545
from ietf.doc.fields import SearchableDocumentsField
4646
from ietf.doc.utils import create_ballot_if_not_open, uppercase_std_abbreviated_name
47-
from ietf.doc.views_search import ad_dashboard_group, ad_dashboard_group_type, shorten_group_name # TODO: red flag that we're importing from views in tests. Move these to utils.
4847
from ietf.group.models import Group, Role
4948
from ietf.group.factories import GroupFactory, RoleFactory
5049
from ietf.ipr.factories import HolderIprDisclosureFactory
@@ -60,6 +59,7 @@
6059
from ietf.utils.test_utils import TestCase
6160
from ietf.utils.text import normalize_text
6261
from ietf.utils.timezone import date_today, datetime_today, DEADLINE_TZINFO, RPC_TZINFO
62+
from ietf.doc.utils_search import AD_WORKLOAD
6363

6464

6565
class SearchTests(TestCase):
@@ -279,43 +279,61 @@ def test_frontpage(self):
279279
self.assertContains(r, "Document Search")
280280

281281
def test_ad_workload(self):
282-
Role.objects.filter(name_id='ad').delete()
283-
ad = RoleFactory(name_id='ad',group__type_id='area',group__state_id='active',person__name='Example Areadirector').person
284-
doc_type_names = ['bofreq', 'charter', 'conflrev', 'draft', 'statchg']
285-
expected = defaultdict(lambda :0)
286-
for doc_type_name in doc_type_names:
287-
if doc_type_name=='draft':
288-
states = State.objects.filter(type='draft-iesg', used=True).values_list('slug', flat=True)
289-
else:
290-
states = State.objects.filter(type=doc_type_name, used=True).values_list('slug', flat=True)
291-
292-
for state in states:
293-
target_num = random.randint(0,2)
282+
Role.objects.filter(name_id="ad").delete()
283+
ad = RoleFactory(
284+
name_id="ad",
285+
group__type_id="area",
286+
group__state_id="active",
287+
person__name="Example Areadirector",
288+
).person
289+
expected = defaultdict(lambda: 0)
290+
for doc_type_slug in AD_WORKLOAD:
291+
for state in AD_WORKLOAD[doc_type_slug]:
292+
target_num = random.randint(0, 2)
294293
for _ in range(target_num):
295-
if doc_type_name == 'draft':
296-
doc = IndividualDraftFactory(ad=ad,states=[('draft-iesg', state),('draft','rfc' if state=='pub' else 'active')])
297-
elif doc_type_name == 'charter':
298-
doc = CharterFactory(ad=ad, states=[(doc_type_name, state)])
299-
elif doc_type_name == 'bofreq':
300-
# Note that the view currently doesn't handle bofreqs
301-
doc = BofreqFactory(states=[(doc_type_name, state)], bofreqresponsibledocevent__responsible=[ad])
302-
elif doc_type_name == 'conflrev':
303-
doc = ConflictReviewFactory(ad=ad, states=State.objects.filter(type_id=doc_type_name, slug=state))
304-
elif doc_type_name == 'statchg':
305-
doc = StatusChangeFactory(ad=ad, states=State.objects.filter(type_id=doc_type_name, slug=state))
306-
else:
307-
# Currently unreachable
308-
doc = DocumentFactory(type_id=doc_type_name, ad=ad, states=[(doc_type_name, state)])
309-
310-
if not slugify(ad_dashboard_group_type(doc)) in ('document', 'none'):
311-
expected[(slugify(ad_dashboard_group_type(doc)), slugify(ad.full_name_as_key()), slugify(shorten_group_name(ad_dashboard_group(doc))))] += 1
312-
313-
url = urlreverse('ietf.doc.views_search.ad_workload')
294+
if (
295+
doc_type_slug == "draft"
296+
or doc_type_slug == "rfc"
297+
and state == "rfcqueue"
298+
):
299+
IndividualDraftFactory(
300+
ad=ad,
301+
states=[
302+
("draft-iesg", state),
303+
("draft", "rfc" if state == "pub" else "active"),
304+
],
305+
)
306+
elif doc_type_slug == "rfc":
307+
WgRfcFactory.create(
308+
states=[("draft", "rfc"), ("draft-iesg", "pub")]
309+
)
310+
311+
elif doc_type_slug == "charter":
312+
CharterFactory(ad=ad, states=[(doc_type_slug, state)])
313+
elif doc_type_slug == "conflrev":
314+
ConflictReviewFactory(
315+
ad=ad,
316+
states=State.objects.filter(
317+
type_id=doc_type_slug, slug=state
318+
),
319+
)
320+
elif doc_type_slug == "statchg":
321+
StatusChangeFactory(
322+
ad=ad,
323+
states=State.objects.filter(
324+
type_id=doc_type_slug, slug=state
325+
),
326+
)
327+
self.client.login(username="ad", password="ad+password")
328+
url = urlreverse("ietf.doc.views_search.ad_workload")
314329
r = self.client.get(url)
315330
self.assertEqual(r.status_code, 200)
316331
q = PyQuery(r.content)
317332
for group_type, ad, group in expected:
318-
self.assertEqual(int(q(f'#{group_type}-{ad}-{group}').text()),expected[(group_type, ad, group)])
333+
self.assertEqual(
334+
int(q(f"#{group_type}-{ad}-{group}").text()),
335+
expected[(group_type, ad, group)],
336+
)
319337

320338
def test_docs_for_ad(self):
321339
ad = RoleFactory(name_id='ad',group__type_id='area',group__state_id='active').person

ietf/doc/utils_search.py

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

1010
from django.conf import settings
1111

12-
from ietf.doc.models import Document, DocAlias, RelatedDocument, DocEvent, TelechatDocEvent, BallotDocEvent
12+
from ietf.doc.models import Document, DocAlias, RelatedDocument, DocEvent, TelechatDocEvent, BallotDocEvent, DocTypeName
1313
from ietf.doc.expire import expirable_drafts
1414
from ietf.doc.utils import augment_docs_and_user_with_user_info
1515
from ietf.meeting.models import SessionPresentation, Meeting, Session
@@ -26,7 +26,7 @@ def fill_in_telechat_date(docs, doc_dict=None, doc_ids=None):
2626
doc_dict = dict((d.pk, d) for d in docs)
2727
doc_ids = list(doc_dict.keys())
2828
if doc_ids is None:
29-
doc_ids = list(doc_dict.keys())
29+
doc_ids = list(doc_dict.keys())
3030

3131
seen = set()
3232
for e in TelechatDocEvent.objects.filter(doc__id__in=doc_ids, type="scheduled_for_telechat").order_by('-time'):
@@ -181,7 +181,7 @@ def augment_docs_with_related_docs_info(docs):
181181
continue
182182
originalDoc = d.related_that_doc('conflrev')[0].document
183183
d.pages = originalDoc.pages
184-
184+
185185
def prepare_document_table(request, docs, query=None, max_results=200):
186186
"""Take a queryset of documents and a QueryDict with sorting info
187187
and return list of documents with attributes filled in for
@@ -283,3 +283,86 @@ def num(i):
283283
h["sort_url"] = "?" + d.urlencode()
284284

285285
return (docs, meta)
286+
287+
288+
# The document types and state slugs to include in the AD dashboard
289+
# and AD doc list, in the order they should be shown.
290+
#
291+
# "rfc" is a custom subset of "draft" that we special-case in the code
292+
# to break out these docs into a separate table.
293+
#
294+
AD_WORKLOAD = {
295+
"draft": [
296+
"pub-req",
297+
"ad-eval",
298+
"lc-req",
299+
"lc",
300+
"writeupw",
301+
# "defer", # probably not a useful state to show, since it's rare
302+
"iesg-eva",
303+
"goaheadw",
304+
"approved",
305+
"ann",
306+
],
307+
"rfc": [
308+
"rfcqueue",
309+
"rfc",
310+
],
311+
"conflrev": [
312+
"needshep",
313+
"adrev",
314+
"iesgeval",
315+
"approved", # synthesized state for all the "appr-" states
316+
# "withdraw", # probably not a useful state to show
317+
],
318+
"statchg": [
319+
"needshep",
320+
"adrev",
321+
"lc-req",
322+
"in-lc",
323+
"iesgeval",
324+
"goahead",
325+
"appr-sent",
326+
# "dead", # probably not a useful state to show
327+
],
328+
"charter": [
329+
"notrev",
330+
"infrev",
331+
"intrev",
332+
"extrev",
333+
"iesgrev",
334+
"approved",
335+
# "replaced", # probably not a useful state to show
336+
],
337+
}
338+
339+
340+
def doc_type(doc):
341+
dt = doc.type.slug
342+
if (
343+
doc.get_state_slug("draft") == "rfc"
344+
or doc.get_state_slug("draft-iesg") == "rfcqueue"
345+
):
346+
dt = "rfc"
347+
return dt
348+
349+
350+
def doc_state(doc):
351+
dt = doc.type.slug
352+
ds = doc.get_state(dt)
353+
if dt == "draft":
354+
dis = doc.get_state("draft-iesg")
355+
if ds.slug == "active" and dis:
356+
return dis.slug
357+
elif dt == "conflrev":
358+
if ds.slug.startswith("appr"):
359+
return "approved"
360+
return ds.slug
361+
362+
363+
def doc_type_name(doc_type):
364+
if doc_type == "rfc":
365+
return "RFC"
366+
if doc_type == "draft":
367+
return "Internet-Draft"
368+
return DocTypeName.objects.get(slug=doc_type).name

0 commit comments

Comments
 (0)