feat: move IETF Activity reports from external text based to HTML pages (#5180)

* feat: move IETF Activity reports from external text based to HTML pages

* fix: use date_today(), fix fencepost problem

* fix: use is_meeting_report template variable instead of is_monthly_report
This commit is contained in:
Ryan Cross 2023-02-27 14:58:59 -08:00 committed by GitHub
parent caf80efd84
commit e469addcb2
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
17 changed files with 111 additions and 297 deletions

View file

@ -1,22 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-
#
# This script requires that the proper virtual python environment has been
# invoked before start
# Set PYTHONPATH and load environment variables for standalone script -----------------
import os, sys
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
os.environ["DJANGO_SETTINGS_MODULE"] = "ietf.settings"
import django
django.setup()
# -------------------------------------------------------------------------------------
from ietf.secr.proceedings.reports import report_id_activity
print(report_id_activity(sys.argv[1], sys.argv[2]), end='')

View file

@ -1,24 +0,0 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# -*- Python -*-
#
# This script requires that the proper virtual python environment has been
# invoked before start
# Set PYTHONPATH and load environment variables for standalone script -----------------
import os, sys
basedir = os.path.abspath(os.path.join(os.path.dirname(__file__), "../.."))
sys.path = [ basedir ] + sys.path
os.environ["DJANGO_SETTINGS_MODULE"] = "ietf.settings"
import django
django.setup()
# -------------------------------------------------------------------------------------
from ietf.secr.proceedings.reports import report_progress_report
# handle unicode characters before attempting to print
output = report_progress_report(sys.argv[1], sys.argv[2])
output = output.replace(chr(160),' ') # replace NO-BREAK SPACE with space
print(output, end='')

View file

@ -96,6 +96,12 @@ class IESGTests(TestCase):
ads = Role.objects.filter(group__type='area', group__state='active', name_id='ad')
self.assertEqual(len(q('.photo')), ads.count())
def test_ietf_activity(self):
url = urlreverse("ietf.iesg.views.ietf_activity")
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
class IESGAgendaTests(TestCase):
def setUp(self):
super().setUp()
@ -542,4 +548,4 @@ class RescheduleOnAgendaTests(TestCase):
self.assertTrue(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat"))
self.assertEqual(draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").telechat_date, d)
self.assertTrue(not draft.latest_event(TelechatDocEvent, "scheduled_for_telechat").returning_item)
self.assertEqual(draft.docevent_set.count(), events_before + 1)
self.assertEqual(draft.docevent_set.count(), events_before + 1)

View file

@ -55,6 +55,7 @@ urlpatterns = [
url(r'^past/documents/$', views.past_documents),
url(r'^agenda/telechat-(?:%(date)s-)?docs.tgz' % settings.URL_REGEXPS, views.telechat_docs_tarfile),
url(r'^discusses/$', views.discusses),
url(r'^ietf-activity/$', views.ietf_activity),
url(r'^milestones/$', views.milestones_needing_review),
url(r'^photos/$', views.photos),
]
]

View file

@ -41,6 +41,7 @@ import json
import os
import tarfile
import time
from dateutil import relativedelta
from django import forms
from django.conf import settings
@ -62,6 +63,7 @@ from ietf.iesg.models import TelechatDate
from ietf.iesg.utils import telechat_page_count
from ietf.ietfauth.utils import has_role, role_required, user_is_person
from ietf.person.models import Person
from ietf.secr.proceedings.proc_utils import get_activity_stats
from ietf.doc.utils_search import fill_in_document_table_attributes, fill_in_telechat_date
from ietf.utils.timezone import date_today, datetime_from_date
@ -528,4 +530,34 @@ def photos(request):
role.last_initial = role.person.last_name()[0]
return render(request, 'iesg/photos.html', {'group_type': 'IESG', 'role': '', 'roles': roles })
def month_choices():
choices = [(str(n).zfill(2), str(n).zfill(2)) for n in range(1, 13)]
return choices
def year_choices():
this_year = date_today().year
choices = [(str(n), str(n)) for n in range(this_year, 2009, -1)]
return choices
class ActivityForm(forms.Form):
month = forms.ChoiceField(choices=month_choices, help_text='Month', required=True)
year = forms.ChoiceField(choices=year_choices, help_text='Year', required=True)
def ietf_activity(request):
# default date range for last month
today = date_today()
edate = today.replace(day=1)
sdate = (edate - datetime.timedelta(days=1)).replace(day=1)
if request.method == 'GET':
form = ActivityForm(request.GET)
if form.is_valid():
month = form.cleaned_data['month']
year = form.cleaned_data['year']
sdate = datetime.date(int(year), int(month), 1)
edate = sdate + relativedelta.relativedelta(months=1)
# always pass back an unbound form to avoid annoying is-valid styling
form = ActivityForm(initial={'month': str(sdate.month).zfill(2), 'year': sdate.year})
context = get_activity_stats(sdate, edate)
context['form'] = form
return render(request, "iesg/ietf_activity_report.html", context)

View file

@ -7530,7 +7530,7 @@ class ProceedingsTests(BaseMeetingTestCase):
)
self.assertNotEqual(
pq('a[href="{}"]'.format(
urlreverse('ietf.meeting.views.proceedings_progress_report', kwargs=dict(num=meeting.number)))
urlreverse('ietf.meeting.views.proceedings_activity_report', kwargs=dict(num=meeting.number)))
),
[],
'Should have a link to activity report',
@ -7696,14 +7696,14 @@ class ProceedingsTests(BaseMeetingTestCase):
response = self.client.get(url)
self.assertContains(response, 'The Internet Engineering Task Force')
def test_proceedings_progress_report(self):
def test_proceedings_activity_report(self):
make_meeting_test_data()
MeetingFactory(type_id='ietf', date=datetime.date(2016,4,3), number="96")
MeetingFactory(type_id='ietf', date=datetime.date(2016,7,14), number="97")
url = urlreverse('ietf.meeting.views.proceedings_progress_report',kwargs={'num':97})
url = urlreverse('ietf.meeting.views.proceedings_activity_report',kwargs={'num':97})
response = self.client.get(url)
self.assertContains(response, 'Progress Report')
self.assertContains(response, 'Activity Report')
def test_feed(self):
meeting = make_meeting_test_data()

View file

@ -92,7 +92,7 @@ type_ietf_only_patterns_id_optional = [
url(r'^proceedings/acknowledgements/$', views.proceedings_acknowledgements),
url(r'^proceedings/attendees/$', views.proceedings_attendees),
url(r'^proceedings/overview/$', views.proceedings_overview),
url(r'^proceedings/progress-report/$', views.proceedings_progress_report),
url(r'^proceedings/activity-report/$', views.proceedings_activity_report),
url(r'^proceedings/materials/$', views_proceedings.material_details),
url(r'^proceedings/materials/(?P<material_type>[a-z_]+)/$', views_proceedings.edit_material),
url(r'^proceedings/materials/(?P<material_type>[a-z_]+)/new/$', views_proceedings.upload_material),

View file

@ -84,7 +84,7 @@ from ietf.meeting.utils import preprocess_meeting_important_dates
from ietf.meeting.utils import new_doc_for_session, write_doc_for_session
from ietf.message.utils import infer_message
from ietf.name.models import SlideSubmissionStatusName, ProceedingsMaterialTypeName, SessionPurposeName
from ietf.secr.proceedings.proc_utils import (get_progress_stats, post_process, import_audio_files,
from ietf.secr.proceedings.proc_utils import (get_activity_stats, post_process, import_audio_files,
create_recording)
from ietf.utils import markdown
from ietf.utils.decorators import require_api_key
@ -3803,8 +3803,8 @@ def proceedings_overview(request, num=None):
'template': template,
})
def proceedings_progress_report(request, num=None):
'''Display Progress Report (stats since last meeting)'''
def proceedings_activity_report(request, num=None):
'''Display Activity Report (stats since last meeting)'''
if not (num and num.isdigit()):
raise Http404
meeting = get_meeting(num)
@ -3812,9 +3812,10 @@ def proceedings_progress_report(request, num=None):
return HttpResponseRedirect(f'{settings.PROCEEDINGS_V1_BASE_URL.format(meeting=meeting)}/progress-report.html')
sdate = meeting.previous_meeting().date
edate = meeting.date
context = get_progress_stats(sdate,edate)
context = get_activity_stats(sdate,edate)
context['meeting'] = meeting
return render(request, "meeting/proceedings_progress_report.html", context)
context['is_meeting_report'] = True
return render(request, "meeting/proceedings_activity_report.html", context)
class OldUploadRedirect(RedirectView):
def get_redirect_url(self, **kwargs):

View file

@ -199,10 +199,10 @@ def send_audio_import_warning(unmatched_files):
# End Recording Functions
# -------------------------------------------------
def get_progress_stats(sdate, edate):
def get_activity_stats(sdate, edate):
'''
This function takes a date range and produces a dictionary of statistics / objects for
use in a progress report. Generally the end date will be the date of the last meeting
use in an activity report. Generally the end date will be the date of the last meeting
and the start date will be the date of the meeting before that.
Data between midnight UTC on the specified dates are included in the stats.

View file

@ -1,106 +0,0 @@
import datetime
from django.template.loader import render_to_string
from django.utils import timezone
from ietf.meeting.models import Meeting
from ietf.doc.models import DocEvent, Document
from ietf.secr.proceedings.proc_utils import get_progress_stats
from ietf.utils.timezone import datetime_from_date
def report_id_activity(start,end):
# get previous meeting
meeting = Meeting.objects.filter(date__lt=timezone.now(),type='ietf').order_by('-date')[0]
syear,smonth,sday = start.split('-')
eyear,emonth,eday = end.split('-')
sdate = datetime_from_date(datetime.date(int(syear),int(smonth),int(sday)), meeting.tz())
edate = datetime_from_date(datetime.date(int(eyear),int(emonth),int(eday)), meeting.tz())
#queryset = Document.objects.filter(type='draft').annotate(start_date=Min('docevent__time'))
new_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
docevent__newrevisiondocevent__rev='00',
docevent__time__gte=sdate,
docevent__time__lte=edate)
new = new_docs.count()
updated = 0
updated_more = 0
for d in new_docs:
updates = d.docevent_set.filter(type='new_revision',time__gte=sdate,time__lte=edate).count()
if updates > 1:
updated += 1
if updates > 2:
updated_more +=1
# calculate total documents updated, not counting new, rev=00
result = set()
events = DocEvent.objects.filter(doc__type='draft',time__gte=sdate,time__lte=edate)
for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'):
result.add(e.doc)
total_updated = len(result)
# calculate sent last call
last_call = events.filter(type='sent_last_call').count()
# calculate approved
approved = events.filter(type='iesg_approved').count()
# get 4 weeks
monday = datetime_from_date(Meeting.get_current_meeting().get_ietf_monday(), meeting.tz())
cutoff = monday + datetime.timedelta(days=3)
ff1_date = cutoff - datetime.timedelta(days=28)
#ff2_date = cutoff - datetime.timedelta(days=21)
#ff3_date = cutoff - datetime.timedelta(days=14)
#ff4_date = cutoff - datetime.timedelta(days=7)
ff_docs = Document.objects.filter(type='draft').filter(docevent__type='new_revision',
docevent__newrevisiondocevent__rev='00',
docevent__time__gte=ff1_date,
docevent__time__lte=cutoff)
ff_new_count = ff_docs.count()
ff_new_percent = format(ff_new_count / float(new),'.0%')
# calculate total documents updated in final four weeks, not counting new, rev=00
result = set()
events = DocEvent.objects.filter(doc__type='draft',time__gte=ff1_date,time__lte=cutoff)
for e in events.filter(type='new_revision').exclude(newrevisiondocevent__rev='00'):
result.add(e.doc)
ff_update_count = len(result)
ff_update_percent = format(ff_update_count / float(total_updated),'.0%')
#aug_docs = augment_with_start_time(new_docs)
'''
ff1_new = aug_docs.filter(start_date__gte=ff1_date,start_date__lt=ff2_date)
ff2_new = aug_docs.filter(start_date__gte=ff2_date,start_date__lt=ff3_date)
ff3_new = aug_docs.filter(start_date__gte=ff3_date,start_date__lt=ff4_date)
ff4_new = aug_docs.filter(start_date__gte=ff4_date,start_date__lt=edate)
ff_new_iD = ff1_new + ff2_new + ff3_new + ff4_new
'''
context = {'meeting':meeting,
'new':new,
'updated':updated,
'updated_more':updated_more,
'total_updated':total_updated,
'last_call':last_call,
'approved':approved,
'ff_new_count':ff_new_count,
'ff_new_percent':ff_new_percent,
'ff_update_count':ff_update_count,
'ff_update_percent':ff_update_percent}
report = render_to_string('proceedings/report_id_activity.txt', context)
return report
def report_progress_report(start_date,end_date):
syear,smonth,sday = start_date.split('-')
eyear,emonth,eday = end_date.split('-')
sdate = datetime.datetime(int(syear),int(smonth),int(sday))
edate = datetime.datetime(int(eyear),int(emonth),int(eday))
context = get_progress_stats(sdate,edate)
report = render_to_string('proceedings/report_progress_report.txt', context)
return report

View file

@ -1,36 +0,0 @@
import datetime
import debug # pyflakes:ignore
from django.utils import timezone
from ietf.doc.factories import DocumentFactory,NewRevisionDocEventFactory
from ietf.secr.proceedings.reports import report_id_activity, report_progress_report
from ietf.utils.test_utils import TestCase
from ietf.meeting.factories import MeetingFactory
class ReportsTestCase(TestCase):
def test_report_id_activity(self):
today = timezone.now()
yesterday = today - datetime.timedelta(days=1)
last_quarter = today - datetime.timedelta(days=3*30)
next_week = today+datetime.timedelta(days=7)
m1 = MeetingFactory(type_id='ietf',date=last_quarter)
m2 = MeetingFactory(type_id='ietf',date=next_week,number=int(m1.number)+1)
doc = DocumentFactory(type_id='draft',time=yesterday,rev="00")
NewRevisionDocEventFactory(doc=doc,time=today,rev="01")
result = report_id_activity(m1.date.strftime("%Y-%m-%d"),m2.date.strftime("%Y-%m-%d"))
self.assertTrue('IETF Activity since last IETF Meeting' in result)
def test_report_progress_report(self):
today = timezone.now()
last_quarter = today - datetime.timedelta(days=3*30)
next_week = today+datetime.timedelta(days=7)
m1 = MeetingFactory(type_id='ietf',date=last_quarter)
m2 = MeetingFactory(type_id='ietf',date=next_week,number=int(m1.number)+1)
result = report_progress_report(m1.date.strftime('%Y-%m-%d'),m2.date.strftime('%Y-%m-%d'))
self.assertTrue('IETF Activity since last IETF Meeting' in result)

View file

@ -1,21 +0,0 @@
IETF Activity since last IETF Meeting
--------------------
IETF Activity since last IETF Meeting ({{ meeting.city }})
{{ new|stringformat:"3s" }} New I-Ds ({{ updated }} of which were updated, some ({{ updated_more }}) more than once)
{{ total_updated|stringformat:"3s" }} I-Ds were updated (Some more than once)
{{ last_call|stringformat:"3s" }} I-Ds Last Called
{{ approved|stringformat:"3s" }} I-Ds approved for publication
In the final 4 weeks before meeting
{{ ff_new_count|stringformat:"3s" }} New I-Ds were received - {{ ff_new_percent }} of total newbies since last meeting
{{ ff_update_count|stringformat:"3s" }} I-Ds were updated - {{ ff_update_percent }} of total updated since last meeting
Week1 0 %
Week2 0 %
Week3 0 %
Week4 0 %
The IESG Secretary.

View file

@ -1,48 +0,0 @@
{% load ams_filters %}
IETF Activity since last IETF Meeting
{{ start_date }} to {{ end_date }}
1) {{ action_events.count }} IESG Protocol and Document Actions this period
{% for event in action_events %}
{{ event.doc.title }} ({{ event.doc.intended_std_level }})
{% endfor %}
2) {{ lc_events.count }} IESG Last Calls issued to the IETF this period
{% for event in lc_events %}
{{ event.doc.title }}
{{ event.doc.file_tag|safe }} ({{ event.doc.intended_std_level }})
{% endfor %}
3) {{ new_groups.count }} New Working Group(s) formed this period
{% for group in new_groups %}
{{ group }} ({{ group.acronym }})
{% endfor %}
4) {{ concluded_groups.count }} Working Group(s) concluded this period
{% for group in concluded_groups %}
{{ group }} ({{ group.acronym }})
{% endfor %}
5) {{ new_docs|length }} new or revised Internet-Drafts this period
(o - Revised Internet-Draft; + - New Internet-Draft)
WG I-D Title <Filename>
------- ------------------------------------------
{% for doc in new_docs %}
({{ doc.group.acronym|stringformat:"8s" }}) {% if doc.rev == "00" %} + {% else %} o {% endif %}{{ doc.title }}
{{ doc.file_tag|safe }}
{% endfor %}
6) {{ rfcs.count }} RFC(s) produced this period
S - Standard; PS - Proposed Standard; DS - Draft Standard;
B - Best Current Practices; E - Experimental; I - Informational
RFC Stat WG Published Title
------- -- ---------- ---------- -----------------------------------------
{% for event in rfcs %}
{{ event.doc.canonical_name|upper }} {{ event.doc.intended_std_level.name|abbr_status|stringformat:"2s" }} ({{ event.doc.group.acronym|stringformat:"8s" }}) {{ event.time|date:"M d" }} {{ event.doc.title }}
{% endfor %}
{{ counts.std }} Standards Track; {{ counts.bcp }} BCP; {{ counts.exp }} Experimental; {{ counts.inf }} Informational

View file

@ -0,0 +1,25 @@
{% extends "base.html" %}
{% load ams_filters ietf_filters django_bootstrap5 %}
{% block title %}IETF Activity Report{% endblock %}
{% block content %}
<h1 class="mt-3">IETF Activity Report</h1>
<h2 class="mt-3"><small class="text-muted">{{ sdate|date:"F Y" }}</small></h2>
<form action="." method="GET">
<div class="row">
<div class="col-2">
{% bootstrap_field form.month show_label=True layout="inline" %}
</div>
<div class="col-2">
{% bootstrap_field form.year layout="inline" %}
</div>
<div class="col-2">
<button type="submit" class="btn btn-primary">Submit</button>
</div>
</div>
</form>
<h3 class="mt-3">Draft Activity</h3>
{% include "meeting/activity_report.html" %}
{% endblock %}

View file

@ -1,16 +1,4 @@
{% extends "base.html" %}
{% load ams_filters ietf_filters cache %}
{% block title %}IETF {{ meeting.number }} Proceedings - Progress Report{% endblock %}
{% block content %}
{% cache 3600 proceedings_progress_report meeting.number %}
<h1>
<a class="text-decoration-none text-reset"
href="{% url 'ietf.meeting.views.proceedings' num=meeting.number %}">
IETF {{ meeting.number }} proceedings
</a>
</h1>
<h2 class="mt-3">IETF Progress Report</h2>
<h3 class="mt-3">{{ sdate|date:"d-F-y" }} to {{ edate|date:"d-F-y" }}</h3>
{% load ams_filters ietf_filters %}
<ul class="progress-section">
<li>{{ actions_count }} IESG Protocol and Document Actions this period</li>
<li>{{ last_calls_count }} IESG Last Calls issued to the IETF this period</li>
@ -18,15 +6,17 @@
{{ new_drafts_count|stringformat:"3s" }} New I-Ds ({{ new_drafts_updated_count }} of which were updated, some ({{ new_drafts_updated_more_count }}) more than once)
</li>
<li>{{ updated_drafts_count|stringformat:"3s" }} I-Ds were updated (Some more than once)</li>
<li>
<h3 class="mt-3">In the final 4 weeks before meeting</h3>
</li>
<li>
{{ ffw_new_count|stringformat:"3s" }} New I-Ds were received - {{ ffw_new_percent }} of total newbies since last meeting
</li>
<li>
{{ ffw_update_count|stringformat:"3s" }} I-Ds were updated - {{ ffw_update_percent }} of total updated since last meeting
</li>
{% if is_meeting_report %}
<li>
<h3 class="mt-3">In the final 4 weeks before meeting</h3>
</li>
<li>
{{ ffw_new_count|stringformat:"3s" }} New I-Ds were received - {{ ffw_new_percent }} of total newbies since last meeting
</li>
<li>
{{ ffw_update_count|stringformat:"3s" }} I-Ds were updated - {{ ffw_update_percent }} of total updated since last meeting
</li>
{% endif %}
</ul>
<h3 class="mt-3">{{ new_groups.count }} New Working Group(s) formed this period</h3>
<ul class="progress-section">
@ -68,5 +58,3 @@
</tbody>
{% endif %}
</table>
{% endcache %}
{% endblock %}

View file

@ -14,7 +14,7 @@ This renders the list of links below the title on the meeting proceedings page.
<a href="{% url 'agenda' num=meeting.number %}">Meeting Agenda</a>
</div>
<div class="proceedings-row">
<a href="{% url 'ietf.meeting.views.proceedings_progress_report' num=meeting.number %}">Activity Report</a>
<a href="{% url 'ietf.meeting.views.proceedings_activity_report' num=meeting.number %}">Activity Report</a>
</div>
<div class="proceedings-row">
<a href="{% url 'ietf.meeting.views.important_dates' num=meeting.number %}">Important Dates</a>

View file

@ -0,0 +1,18 @@
{% extends "base.html" %}
{% load ams_filters ietf_filters cache %}
{% block title %}IETF {{ meeting.number }} Proceedings - Activity Report{% endblock %}
{% block content %}
{% cache 3600 proceedings_activity_report meeting.number %}
<h1>
<a class="text-decoration-none text-reset"
href="{% url 'ietf.meeting.views.proceedings' num=meeting.number %}">
IETF {{ meeting.number }} proceedings
</a>
</h1>
<h2 class="mt-3">IETF Activity Report</h2>
<h3 class="mt-3">{{ sdate|date:"d-F-y" }} to {{ edate|date:"d-F-y" }}</h3>
{% include "meeting/activity_report.html" %}
{% endcache %}
{% endblock %}