feat: Add requests summary (#5439)
* feat: Add summary data to meeting/requests * feat: Add Group Type and Purpose tables to requests summary * fix: use self.assertXX instead of raw assert --------- Co-authored-by: Robert Sparks <rjsparks@nostrum.com>
This commit is contained in:
parent
0f1a6c960f
commit
1bd5c5e2f1
|
@ -49,6 +49,7 @@ from ietf.meeting.utils import finalize, condition_slide_order
|
|||
from ietf.meeting.utils import add_event_info_to_session_qs
|
||||
from ietf.meeting.utils import create_recording, get_next_sequence
|
||||
from ietf.meeting.views import session_draft_list, parse_agenda_filter_params, sessions_post_save, agenda_extract_schedule
|
||||
from ietf.meeting.views import get_summary_by_area, get_summary_by_type, get_summary_by_purpose
|
||||
from ietf.name.models import SessionStatusName, ImportantDateName, RoleName, ProceedingsMaterialTypeName
|
||||
from ietf.utils.decorators import skip_coverage
|
||||
from ietf.utils.mail import outbox, empty_outbox, get_payload_text
|
||||
|
@ -6589,6 +6590,28 @@ class ImportNotesTests(TestCase):
|
|||
|
||||
class SessionTests(TestCase):
|
||||
|
||||
def test_get_summary_by_area(self):
|
||||
meeting = make_meeting_test_data(meeting=MeetingFactory(type_id='ietf', number='100'))
|
||||
sessions = Session.objects.filter(meeting=meeting).with_current_status()
|
||||
data = get_summary_by_area(sessions)
|
||||
self.assertEqual(data[0][0], 'Duration')
|
||||
self.assertGreater(len(data), 2)
|
||||
self.assertEqual(data[-1][0], 'Total Hours')
|
||||
|
||||
def test_get_summary_by_type(self):
|
||||
meeting = make_meeting_test_data(meeting=MeetingFactory(type_id='ietf', number='100'))
|
||||
sessions = Session.objects.filter(meeting=meeting).with_current_status()
|
||||
data = get_summary_by_type(sessions)
|
||||
self.assertEqual(data[0][0], 'Group Type')
|
||||
self.assertGreater(len(data), 2)
|
||||
|
||||
def test_get_summary_by_purpose(self):
|
||||
meeting = make_meeting_test_data(meeting=MeetingFactory(type_id='ietf', number='100'))
|
||||
sessions = Session.objects.filter(meeting=meeting).with_current_status()
|
||||
data = get_summary_by_purpose(sessions)
|
||||
self.assertEqual(data[0][0], 'Purpose')
|
||||
self.assertGreater(len(data), 2)
|
||||
|
||||
def test_meeting_requests(self):
|
||||
meeting = MeetingFactory(type_id='ietf')
|
||||
|
||||
|
|
|
@ -101,7 +101,9 @@ from .forms import (InterimMeetingModelForm, InterimAnnounceForm, InterimSession
|
|||
InterimCancelForm, InterimSessionInlineFormSet, RequestMinutesForm,
|
||||
UploadAgendaForm, UploadBlueSheetForm, UploadMinutesForm, UploadSlidesForm)
|
||||
|
||||
request_summary_exclude_group_types = ['team']
|
||||
|
||||
|
||||
def get_interim_menu_entries(request):
|
||||
'''Setup menu entries for interim meeting view tabs'''
|
||||
entries = []
|
||||
|
@ -2238,6 +2240,70 @@ def agenda_json(request, num=None):
|
|||
response['Last-Modified'] = format_date_time(timegm(last_modified.timetuple()))
|
||||
return response
|
||||
|
||||
def request_summary_filter(session):
|
||||
if (session.group.area is None
|
||||
or session.group.type.slug in request_summary_exclude_group_types
|
||||
or session.current_status == 'notmeet'):
|
||||
return False
|
||||
return True
|
||||
|
||||
def get_area_column(area):
|
||||
if area is None:
|
||||
return ''
|
||||
if area.type.slug in ['rfcedtyp']:
|
||||
name = 'OTHER'
|
||||
else:
|
||||
name = area.acronym.upper()
|
||||
return name
|
||||
|
||||
def get_summary_by_area(sessions):
|
||||
"""Returns summary by area for list of session requests.
|
||||
Summary is a two dimensional array[row=session duration][col=session area count]
|
||||
It also includes row and column headers as well as a totals row.
|
||||
"""
|
||||
|
||||
# first build a dictionary of counts, key=(duration,area)
|
||||
durations = set()
|
||||
areas = set()
|
||||
duration_totals = defaultdict(int)
|
||||
data = defaultdict(int)
|
||||
for session in sessions:
|
||||
area_column = get_area_column(session.group.area)
|
||||
duration = session.requested_duration.seconds / 3600
|
||||
key = (duration, area_column)
|
||||
data[key] = data[key] + 1
|
||||
durations.add(duration)
|
||||
areas.add(area_column)
|
||||
duration_totals[duration] = duration_totals[duration] + 1
|
||||
|
||||
# build two dimensional array for use in template
|
||||
rows = []
|
||||
sorted_areas = sorted(areas)
|
||||
# move "other" to end
|
||||
if 'OTHER' in sorted_areas:
|
||||
sorted_areas.remove('OTHER')
|
||||
sorted_areas.append('OTHER')
|
||||
# add header row
|
||||
rows.append(['Duration'] + sorted_areas + ['TOTAL SLOTS', 'TOTAL HOURS'])
|
||||
for duration in sorted(durations):
|
||||
rows.append([duration] + [data[(duration, a)] for a in sorted_areas] + [duration_totals[duration]] + [duration_totals[duration] * duration])
|
||||
# add total row
|
||||
rows.append(['Total Slots'] + [sum([rows[r][c] for r in range(1, len(rows))]) for c in range(1, len(rows[0]))])
|
||||
rows.append(['Total Hours'] + [sum([d * data[(d, area)] for d in durations]) for area in sorted_areas])
|
||||
return rows
|
||||
|
||||
def get_summary_by_type(sessions):
|
||||
counter = Counter([s.group.type.name for s in sessions])
|
||||
data = counter.most_common()
|
||||
data.insert(0, ('Group Type', 'Count'))
|
||||
return data
|
||||
|
||||
def get_summary_by_purpose(sessions):
|
||||
counter = Counter([s.purpose.name for s in sessions])
|
||||
data = counter.most_common()
|
||||
data.insert(0, ('Purpose', 'Count'))
|
||||
return data
|
||||
|
||||
def meeting_requests(request, num=None):
|
||||
meeting = get_meeting(num)
|
||||
groups_to_show = Group.objects.filter(
|
||||
|
@ -2253,7 +2319,7 @@ def meeting_requests(request, num=None):
|
|||
).with_current_status().with_requested_by().exclude(
|
||||
requested_by=0
|
||||
).prefetch_related(
|
||||
"group","group__ad_role__person"
|
||||
"group", "group__ad_role__person", "group__type"
|
||||
)
|
||||
)
|
||||
|
||||
|
@ -2276,12 +2342,14 @@ def meeting_requests(request, num=None):
|
|||
)
|
||||
|
||||
groups_not_meeting = groups_to_show.exclude(
|
||||
acronym__in = [session.group.acronym for session in sessions]
|
||||
acronym__in=[session.group.acronym for session in sessions]
|
||||
).order_by(
|
||||
"parent__acronym",
|
||||
"acronym",
|
||||
).prefetch_related("parent")
|
||||
|
||||
summary_sessions = list(filter(request_summary_filter, sessions))
|
||||
|
||||
return render(
|
||||
request,
|
||||
"meeting/requests.html",
|
||||
|
@ -2289,6 +2357,9 @@ def meeting_requests(request, num=None):
|
|||
"meeting": meeting,
|
||||
"sessions": sessions,
|
||||
"groups_not_meeting": groups_not_meeting,
|
||||
"summary_by_area": get_summary_by_area(summary_sessions),
|
||||
"summary_by_group_type": get_summary_by_type(summary_sessions),
|
||||
"summary_by_purpose": get_summary_by_purpose(summary_sessions),
|
||||
},
|
||||
)
|
||||
|
||||
|
|
|
@ -15,6 +15,68 @@
|
|||
{% if meeting.venue_name %}– {{ meeting.venue_name }}{% endif %}
|
||||
</small>
|
||||
</h1>
|
||||
|
||||
<h2 class="mt-3">Requests Summary</h2>
|
||||
<div class="mt-2">
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
{% for row in summary_by_area %}
|
||||
<tr>
|
||||
{% if forloop.first %}
|
||||
{% for col in row %}
|
||||
<th scope="col" class="table-primary">{{ col }}</th>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for col in row %}
|
||||
<td>{{ col }}</td>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<div class="col">
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
{% for row in summary_by_group_type %}
|
||||
<tr>
|
||||
{% if forloop.first %}
|
||||
{% for col in row %}
|
||||
<th scope="col" class="table-primary">{{ col }}</th>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for col in row %}
|
||||
<td>{{ col }}</td>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col">
|
||||
<table class="table table-sm">
|
||||
<tbody>
|
||||
{% for row in summary_by_purpose %}
|
||||
<tr>
|
||||
{% if forloop.first %}
|
||||
{% for col in row %}
|
||||
<th scope="col" class="table-primary">{{ col }}</th>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for col in row %}
|
||||
<td>{{ col }}</td>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% regroup sessions by display_area as area_sessions %}
|
||||
{% for area in area_sessions %}
|
||||
<h2 class="mt-5" id="{% firstof area.grouper.acronym "other-groups" %}">
|
||||
|
|
Loading…
Reference in a new issue