Skip to content

Commit

Permalink
Retrieve session agenda, slides, and minutes each time agenda modal i…
Browse files Browse the repository at this point in the history
…s opened. Fixes #3050. Commit ready for merge.

 - Legacy-Id: 18651
  • Loading branch information
jennifer-richards committed Oct 30, 2020
1 parent d67b298 commit bbf04c3
Show file tree
Hide file tree
Showing 7 changed files with 278 additions and 66 deletions.
108 changes: 107 additions & 1 deletion ietf/meeting/tests_js.py
Expand Up @@ -18,6 +18,7 @@
import debug # pyflakes:ignore

from ietf.doc.factories import DocumentFactory
from ietf.doc.models import State
from ietf.group import colors
from ietf.person.models import Person
from ietf.group.models import Group
Expand Down Expand Up @@ -783,7 +784,7 @@ def assert_agenda_view_filter_matches_ics_filter(self, filter_string):
ics_url = self.absreverse('ietf.meeting.views.agenda_ical')

# parse out the events
agenda_rows = self.driver.find_elements_by_css_selector('[id^="row-"')
agenda_rows = self.driver.find_elements_by_css_selector('[id^="row-"]')
visible_rows = [r for r in agenda_rows if r.is_displayed()]
sessions = [self.session_from_agenda_row_id(row.get_attribute("id"))
for row in visible_rows]
Expand All @@ -797,6 +798,111 @@ def assert_agenda_view_filter_matches_ics_filter(self, filter_string):
expected_event_uids=expected_uids,
expected_event_count=len(sessions))

def test_session_materials_modal(self):
"""Test opening and re-opening a session materals modal
This currently only tests the slides to ensure that changes to these are picked up
without reloading the main agenda page. This should also test that the agenda and
minutes are displayed and updated correctly, but problems with WebDriver/Selenium/Chromedriver
are blocking this.
"""
session = self.meeting.session_set.filter(group__acronym="mars").first()
assignment = session.official_timeslotassignment()
slug = assignment.slug()

url = self.absreverse('ietf.meeting.views.agenda')
self.driver.get(url)

# modal should start hidden
modal_div = self.driver.find_element_by_css_selector('div#modal-%s' % slug)
self.assertFalse(modal_div.is_displayed())

# Click the 'materials' button
open_modal_button = WebDriverWait(self.driver, 2).until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, '[data-target="#modal-%s"]' % slug)
),
'Modal open button not found or not clickable',
)
open_modal_button.click()
WebDriverWait(self.driver, 2).until(
expected_conditions.visibility_of(modal_div),
'Modal did not become visible after clicking open button',
)

# Check that we have the expected slides
not_deleted_slides = session.materials.filter(
type='slides'
).exclude(
states__type__slug='slides',states__slug='deleted'
)
self.assertGreater(not_deleted_slides.count(), 0) # make sure this isn't a pointless test
for slide in not_deleted_slides:
anchor = self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
self.assertIsNotNone(anchor)

deleted_slides = session.materials.filter(
type='slides', states__type__slug='slides', states__slug='deleted'
)
self.assertGreater(deleted_slides.count(), 0) # make sure this isn't a pointless test
for slide in deleted_slides:
with self.assertRaises(NoSuchElementException):
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)

# Now close the modal
close_modal_button = WebDriverWait(self.driver, 2).until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, '.modal-footer button[data-dismiss="modal"]')
),
'Modal close button not found or not clickable',
)
close_modal_button.click()
WebDriverWait(self.driver, 2).until(
expected_conditions.invisibility_of_element(modal_div),
'Modal was not hidden after clicking close button',
)

# Modify the session info
newly_deleted_slide = not_deleted_slides.first()
newly_undeleted_slide = deleted_slides.first()
newly_deleted_slide.set_state(State.objects.get(type="slides", slug="deleted"))
newly_undeleted_slide.set_state(State.objects.get(type="slides", slug="active"))

# Click the 'materials' button
open_modal_button = WebDriverWait(self.driver, 2).until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, '[data-target="#modal-%s"]' % slug)
),
'Modal open button not found or not clickable for refresh test',
)
open_modal_button.click()
WebDriverWait(self.driver, 2).until(
expected_conditions.visibility_of(modal_div),
'Modal did not become visible after clicking open button for refresh test',
)

# Check that we now see the updated slides
not_deleted_slides = session.materials.filter(
type='slides'
).exclude(
states__type__slug='slides',states__slug='deleted'
)
self.assertNotIn(newly_deleted_slide, not_deleted_slides)
self.assertIn(newly_undeleted_slide, not_deleted_slides)
for slide in not_deleted_slides:
anchor = self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)
self.assertIsNotNone(anchor)

deleted_slides = session.materials.filter(
type='slides', states__type__slug='slides', states__slug='deleted'
)
self.assertIn(newly_deleted_slide, deleted_slides)
self.assertNotIn(newly_undeleted_slide, deleted_slides)
for slide in deleted_slides:
with self.assertRaises(NoSuchElementException):
self.driver.find_element_by_xpath('//a[text()="%s"]' % slide.title)


@skipIf(skip_selenium, skip_message)
class InterimTests(MeetingTestCase):
def setUp(self):
Expand Down
60 changes: 54 additions & 6 deletions ietf/meeting/tests_views.py
Expand Up @@ -158,12 +158,16 @@ def test_meeting_agenda(self):
self.assertIn(time_interval, agenda_content)
self.assertIn(registration_text, agenda_content)

# Make sure there's a frame for the agenda and it points to the right place
self.assertTrue(any([session.materials.get(type='agenda').get_href() in x.attrib["data-src"] for x in q('tr div.modal-body div.frame')]))

# Make sure undeleted slides are present and deleted slides are not
self.assertTrue(any([session.materials.filter(type='slides').exclude(states__type__slug='slides',states__slug='deleted').first().title in x.text for x in q('tr div.modal-body ul a')]))
self.assertFalse(any([session.materials.filter(type='slides',states__type__slug='slides',states__slug='deleted').first().title in x.text for x in q('tr div.modal-body ul a')]))
# Make sure there's a frame for the session agenda and it points to the right place
assignment = session.official_timeslotassignment()
assignment_url = urlreverse('ietf.meeting.views.assignment_materials',
kwargs=dict(assignment_id=assignment.pk))
self.assertTrue(
any(
[assignment_url in x.attrib["data-src"]
for x in q('tr div.modal-body div.assignment-materials')]
)
)

# future meeting, no agenda
r = self.client.get(urlreverse("ietf.meeting.views.agenda", kwargs=dict(num=future_meeting.number)))
Expand Down Expand Up @@ -844,6 +848,50 @@ def test_cancelled_ics(self):
self.assertIn('STATUS:CANCELLED',unicontent(r))
self.assertNotIn('STATUS:CONFIRMED',unicontent(r))

def test_assignment_materials(self):
meeting = make_meeting_test_data()
session = Session.objects.filter(meeting=meeting, group__acronym="mars").first()

for assignment in session.timeslotassignments.all():
url = urlreverse('ietf.meeting.views.assignment_materials',
kwargs=dict(assignment_id=assignment.pk))
r = self.client.get(url)
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)

agenda_div = q('div.agenda-frame')
self.assertIsNotNone(agenda_div)
self.assertEqual(agenda_div.attr('data-src'), session.agenda().get_href())

minutes_div = q('div.minutes-frame')
self.assertIsNotNone(minutes_div)
self.assertEqual(minutes_div.attr('data-src'), session.minutes().get_href())

# Make sure undeleted slides are present and deleted slides are not
not_deleted_slides = session.materials.filter(
type='slides'
).exclude(
states__type__slug='slides',states__slug='deleted'
)
self.assertGreater(not_deleted_slides.count(), 0) # make sure this isn't a pointless test

deleted_slides = session.materials.filter(
type='slides', states__type__slug='slides', states__slug='deleted'
)
self.assertGreater(deleted_slides.count(), 0) # make sure this isn't a pointless test

# live slides should be found
for slide in not_deleted_slides:
self.assertTrue(q('ul li a:contains("%s")' % slide.title))

# deleted slides should not be found
for slide in deleted_slides:
self.assertFalse(q('ul li a:contains("%s")' % slide.title))


for slide in session.slides():
self.assertContains(r, slide.title)

class ReorderSlidesTests(TestCase):

def test_add_slides_to_session(self):
Expand Down
1 change: 1 addition & 0 deletions ietf/meeting/urls.py
Expand Up @@ -111,6 +111,7 @@
# First patterns which start with unique strings
url(r'^$', views.current_materials),
url(r'^ajax/get-utc/?$', views.ajax_get_utc),
url(r'^assignment/(?P<assignment_id>\d+)/materials.html$', views.assignment_materials),
url(r'^interim/announce/?$', views.interim_announce),
url(r'^interim/announce/(?P<number>[A-Za-z0-9._+-]+)/?$', views.interim_send_announcement),
url(r'^interim/skip_announce/(?P<number>[A-Za-z0-9._+-]+)/?$', views.interim_skip_announcement),
Expand Down
12 changes: 11 additions & 1 deletion ietf/meeting/views.py
Expand Up @@ -242,7 +242,6 @@ def materials_document(request, document, num=None, ext=None):
raise Http404("File not found: %s" % filename)

old_proceedings_format = meeting.number.isdigit() and int(meeting.number) <= 96

if settings.MEETING_MATERIALS_SERVE_LOCALLY or old_proceedings_format:
with io.open(filename, 'rb') as file:
bytes = file.read()
Expand Down Expand Up @@ -1301,6 +1300,17 @@ def diff_schedules(request, num):
'to_schedule': to_schedule,
})

@ensure_csrf_cookie
def assignment_materials(request, assignment_id):
"""Assignment details for agenda page pop-up"""
assignments = SchedTimeSessAssignment.objects.filter(pk=int(assignment_id))
if len(assignments) == 0:
raise Http404('No such assignment')
assert len(assignments) == 1
meeting = assignments[0].timeslot.meeting # timeslot is guaranteed to be non-null
assignments = preprocess_assignments_for_agenda(assignments, meeting)
assignment = assignments[0]
return render(request, 'meeting/assignment_materials.html', dict(item=assignment))

@ensure_csrf_cookie
def agenda(request, num=None, name=None, base=None, ext=None, owner=None, utc=""):
Expand Down
81 changes: 60 additions & 21 deletions ietf/templates/meeting/agenda.html
Expand Up @@ -25,6 +25,9 @@
background-color: inherit !important;
border: none !important;
}
.assignment-materials .agenda-frame,.minutes-frame {
white-space: normal;
}
{% endblock %}

{% block bodyAttrs %}data-spy="scroll" data-target="#affix"{% endblock %}
Expand Down Expand Up @@ -389,12 +392,30 @@ <h2>
agenda_filter.set_update_callback(update_view);
agenda_filter.enable();

$(".modal").on("show.bs.modal", function () {
var i = $(this).find(".frame");
if ($(i).data("src")) {
$.get($(i).data("src"), function (data, status, xhr) {
/**
* Retrieve and display materials for a session
*
* If output_elt exists and has a "data-src" attribute, retrieves the document
* from that URL and displays under output_elt. Handles text/plain, text/markdown,
* and text/html.
*
* @param output_elt Element, probably a div, to hold the output
*/
function retrieve_session_materials(output_elt) {
if (!output_elt) {return;}
output_elt = $(output_elt);
var data_src = output_elt.attr("data-src");
if (!data_src) {
output_elt.html("<p>Error: missing data-src attribute</p>");
} else {
output_elt.html("<p>Loading " + data_src + "...</p>");
outer_xhr = $.get(data_src)
outer_xhr.done(function(data, status, xhr) {
var t = xhr.getResponseHeader("content-type");
if (t.indexOf("text/plain") > -1) {
if (!t) {
data = "<p>Error retrieving " + data_src
+ ": Missing content-type in response header</p>";
} else if (t.indexOf("text/plain") > -1) {
data = "<pre class='agenda'>" + data + "</pre>";
} else if (t.indexOf("text/markdown") > -1) {
data = "<pre class='agenda'>" + data + "</pre>";
Expand All @@ -403,25 +424,43 @@ <h2>
} else {
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
}
$(i).html(data);
});
output_elt.html(data);
}).fail(function() {
output_elt.html("<p>Error retrieving " + data_src
+ ": (" + outer_xhr.status.toString() + ") "
+ outer_xhr.statusText + "</p>");
})
}
var j = $(this).find(".frame2");
if ($(j).data("src")) {
$.get($(j).data("src"), function (data, status, xhr) {
var t = xhr.getResponseHeader("content-type");
if (t.indexOf("text/plain") > -1) {
data = "<pre class='agenda'>" + data + "</pre>";
} else if (t.indexOf("text/markdown") > -1) {
data = "<pre class='agenda'>" + data + "</pre>";
} else if(t.indexOf("text/html") > -1) {
// nothing to do here
} else {
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
}
$(j).html(data);
}

/**
* Retrieve contents of a session materials modal
*
* Expects output_elt to exist and have a "data-src" attribute. Retrieves the
* contents of that URL, then attempts to populate the .agenda-frame and
* .minutes-frame elements.
*
* @param output_elt Element, probably a div, to hold the output
*/
function retrieve_session_modal(output_elt) {
if (!output_elt) {return;}
output_elt = $(output_elt);
var data_src = output_elt.attr("data-src");
if (!data_src) {
output_elt.html("<p>Error: missing data-src attribute</p>");
} else {
output_elt.html("<p>Loading...</p>");
$.get(data_src).done(function(data) {
output_elt.html(data);
retrieve_session_materials(output_elt.find(".agenda-frame"));
retrieve_session_materials(output_elt.find(".minutes-frame"));
});
}
}

$(".modal").on("show.bs.modal", function () {
retrieve_session_modal($(this).find(".assignment-materials"));
});

</script>
{% endblock %}
42 changes: 42 additions & 0 deletions ietf/templates/meeting/assignment_materials.html
@@ -0,0 +1,42 @@
{# Copyright The IETF Trust 2015-2020, All Rights Reserved #}
{% load origin %}{% origin %}
{% load static %}
{% load textfilters %}
{% load ietf_filters %}
{% with item.session.agenda as agenda %}
{% if agenda %}
{% if agenda.file_extension == "txt" or agenda.file_extension == "md" or agenda.file_extension == "html" or agenda.file_extension == "htm" %}
<h4>Agenda</h4>
<div class="agenda-frame" data-src="{{ agenda.get_href }}"></div>
{% else %}
<span class="label label-info">Agenda submitted as {{ agenda.file_extension|upper }}</span>
{% endif %}
{% else %}
<span class="label label-warning">No agenda submitted</span>
{% endif %}
{% endwith %}

{% if item.session.slides %}
<h4>Slides</h4>
<ul class="fa-ul list-unstyled">
{% for slide in item.session.slides %}
<li>
<span class="fa-li fa fa-file-{{ slide.file_extension|lower }}-o"></span>
<a href="{{ slide.get_versionless_href }}">{{ slide.title|clean_whitespace }}</a>
</li>
{% endfor %}
</ul>
{% endif %}

{% with item.session.minutes as minutes %}
{% if minutes %}
{% if minutes.file_extension == "txt" or minutes.file_extension == "md" or minutes.file_extension == "html" or minutes.file_extension == "htm" %}
<h4>Minutes</h4>
<div class="minutes-frame" data-src="{{ minutes.get_href }}"></div>
{% else %}
<span class="label label-info">Minutes submitted as {{ minutes.file_extension|upper }}</span>
{% endif %}
{% else %}
<span class="label label-warning">No minutes submitted</span>
{% endif %}
{% endwith %}

0 comments on commit bbf04c3

Please sign in to comment.