Fix broken meeting materials button on upcoming meetings page. Fixes #3278. Commit ready for merge.

- Legacy-Id: 19164
This commit is contained in:
Jennifer Richards 2021-06-28 18:57:20 +00:00
parent 058f007502
commit 044293b4a9
10 changed files with 164 additions and 129 deletions

View file

@ -6,7 +6,8 @@ import debug # pyflakes:ignore
from ietf.doc.factories import WgDraftFactory, DocumentAuthorFactory
from ietf.person.factories import PersonFactory
from ietf.person.models import Person
from ietf.utils.jstest import IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled
from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled,
presence_of_element_child_by_css_selector )
if selenium_enabled():
from selenium.webdriver.common.by import By
@ -14,19 +15,6 @@ if selenium_enabled():
from selenium.webdriver.support import expected_conditions
class presence_of_element_child_by_css_selector:
"""Wait for presence of a child of a WebElement matching a CSS selector
This is a condition class for use with WebDriverWait.
"""
def __init__(self, element, child_selector):
self.element = element
self.child_selector = child_selector
def __call__(self, driver):
child = self.element.find_element_by_css_selector(self.child_selector)
return child if child is not None else False
@ifSeleniumEnabled
class EditAuthorsTests(IetfSeleniumTestCase):
def setUp(self):

View file

@ -31,7 +31,8 @@ from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session,
Meeting, SchedulingEvent, SessionStatusName)
from ietf.meeting.utils import add_event_info_to_session_qs
from ietf.utils.test_utils import assert_ical_response_is_valid
from ietf.utils.jstest import IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled
from ietf.utils.jstest import ( IetfSeleniumTestCase, ifSeleniumEnabled, selenium_enabled,
presence_of_element_child_by_css_selector )
from ietf import settings
if selenium_enabled():
@ -1572,6 +1573,7 @@ class InterimTests(IetfSeleniumTestCase):
sg_sess.save()
sg_slot.save()
self.wait = WebDriverWait(self.driver, 2)
def tearDown(self):
settings.AGENDA_PATH = self.saved_agenda_path
@ -1647,7 +1649,7 @@ class InterimTests(IetfSeleniumTestCase):
def assert_upcoming_meeting_calendar(self, visible_meetings=None):
"""Assert that correct items are sent to the calendar"""
def advance_month():
button = WebDriverWait(self.driver, 2).until(
button = self.wait.until(
expected_conditions.element_to_be_clickable(
(By.CSS_SELECTOR, 'div#calendar button.fc-next-button')))
self.scroll_to_element(button)
@ -1850,8 +1852,6 @@ class InterimTests(IetfSeleniumTestCase):
self.do_upcoming_view_filter_test('?show=mars , ames &hide= ames', meetings)
def test_upcoming_view_time_zone_selection(self):
wait = WebDriverWait(self.driver, 2)
def _assert_interim_tz_correct(sessions, tz):
zone = pytz.timezone(tz)
for session in sessions:
@ -1897,7 +1897,7 @@ class InterimTests(IetfSeleniumTestCase):
# wait for the select box to be updated - look for an arbitrary time zone to be in
# its options list to detect this
arbitrary_tz = 'America/Halifax'
arbitrary_tz_opt = wait.until(
arbitrary_tz_opt = self.wait.until(
expected_conditions.presence_of_element_located(
(By.CSS_SELECTOR, '#timezone-select > option[value="%s"]' % arbitrary_tz)
)
@ -1923,7 +1923,7 @@ class InterimTests(IetfSeleniumTestCase):
# click 'utc' button
utc_tz_link.click()
wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
@ -1935,7 +1935,7 @@ class InterimTests(IetfSeleniumTestCase):
# click back to 'local'
local_tz_link.click()
wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
@ -1947,7 +1947,7 @@ class InterimTests(IetfSeleniumTestCase):
# Now select a different item from the select input
arbitrary_tz_opt.click()
wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertTrue(arbitrary_tz_opt.is_selected())
@ -1960,7 +1960,7 @@ class InterimTests(IetfSeleniumTestCase):
# Now repeat those tests using the widgets at the bottom of the page
# click 'utc' button
utc_tz_bottom_link.click()
wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.wait.until(expected_conditions.element_to_be_selected(utc_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
@ -1972,7 +1972,7 @@ class InterimTests(IetfSeleniumTestCase):
# click back to 'local'
local_tz_bottom_link.click()
wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.wait.until(expected_conditions.element_to_be_selected(local_tz_opt))
self.assertTrue(local_tz_opt.is_selected())
self.assertTrue(local_tz_bottom_opt.is_selected())
self.assertFalse(arbitrary_tz_opt.is_selected())
@ -1984,7 +1984,7 @@ class InterimTests(IetfSeleniumTestCase):
# Now select a different item from the select input
arbitrary_tz_bottom_opt.click()
wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.wait.until(expected_conditions.element_to_be_selected(arbitrary_tz_opt))
self.assertFalse(local_tz_opt.is_selected())
self.assertFalse(local_tz_bottom_opt.is_selected())
self.assertTrue(arbitrary_tz_opt.is_selected())
@ -1994,6 +1994,52 @@ class InterimTests(IetfSeleniumTestCase):
_assert_interim_tz_correct(sessions, arbitrary_tz)
_assert_ietf_tz_correct(ietf_meetings, arbitrary_tz)
def test_upcoming_materials_modal(self):
"""Test opening and closing a materals modal
This does not test dynamic reloading of the meeting materials - it relies on the main
agenda page testing that. If the materials modal handling diverges between here and
there, this should be updated to include that test.
"""
url = self.absreverse('ietf.meeting.views.upcoming')
self.driver.get(url)
interim = self.displayed_interims(['mars'])[0]
session = interim.session_set.first()
assignment = session.official_timeslotassignment()
slug = assignment.slug()
# 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 = self.wait.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()
self.wait.until(
expected_conditions.visibility_of(modal_div),
'Modal did not become visible after clicking open button',
)
# Now close the modal
close_modal_button = self.wait.until(
presence_of_element_child_by_css_selector(
modal_div,
'.modal-footer button[data-dismiss="modal"]',
),
'Modal close button not found or not clickable',
)
close_modal_button.click()
self.wait.until(
expected_conditions.invisibility_of_element(modal_div),
'Modal was not hidden after clicking close button',
)
# The following are useful debugging tools

View file

@ -0,0 +1,84 @@
// Copyright The IETF Trust 2021, All Rights Reserved
/*
Javascript support for the materials modal rendered by session_agenda_include.html
Requires jquery be loaded
*/
var agenda_materials; // public interface
(function() {
'use strict';
/**
* 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>");
var outer_xhr = $.get(data_src)
outer_xhr.done(function(data, status, xhr) {
var t = xhr.getResponseHeader("content-type");
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>";
} else if(t.indexOf("text/html") > -1) {
// nothing to do here
} else {
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
}
output_elt.html(data);
}).fail(function() {
output_elt.html("<p>Error retrieving " + data_src
+ ": (" + outer_xhr.status.toString() + ") "
+ outer_xhr.statusText + "</p>");
})
}
}
/**
* 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"));
});
}
}
$(document).ready(function() {
$(".modal").on("show.bs.modal", function () {
retrieve_session_modal($(this).find(".session-materials"));
});
})
})();

View file

@ -469,80 +469,11 @@
update_ical_links(filter_params)
}
/**
* 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) {
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>";
} else if(t.indexOf("text/html") > -1) {
// nothing to do here
} else {
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
}
output_elt.html(data);
}).fail(function() {
output_elt.html("<p>Error retrieving " + data_src
+ ": (" + outer_xhr.status.toString() + ") "
+ outer_xhr.statusText + "</p>");
})
}
}
/**
* 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(".session-materials"));
});
</script>
<script src="{% static 'moment/min/moment.min.js' %}"></script>
<script src="{% static 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' %}"></script>
<script src="{% static 'ietf/js/agenda/timezone.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_materials.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_timezone.js' %}"></script>
<script>

View file

@ -5,7 +5,8 @@
{% origin %}
{% with item=session.official_timeslotassignment acronym=session.historic_group.acronym %}
{% if session.agenda and show_agenda %}
{% include "meeting/session_agenda_include.html" %}
{# Note: if called with show_agenda=True, calling template must load agenda_materials.js, needed by session_agenda_include.html #}
{% include "meeting/session_agenda_include.html" with slug=item.slug session=session timeslot=item.timeslot only %}
<!-- agenda pop-up button -->
<a class="" data-toggle="modal" data-target="#modal-{{item.slug}}" title="Show meeting materials"><span class="fa fa-fw fa-arrows-alt"></span></a>
<!-- materials tar file -->

View file

@ -1,5 +1,5 @@
{# Copyright The IETF Trust 2015, All Rights Reserved #}
{# expects slug, session, and timeslot to be in the context #}
{# expects slug, session, and timeslot to be in the context. Calling template must load the agenda_materials.js script. #}
{% load origin %}{% origin %}
{% load static %}
{% load textfilters %}

View file

@ -8,6 +8,7 @@
<span id="session-buttons-{{session.pk}}" class="text-nowrap">
{% with acronym=session.historic_group.acronym %}
{% if session.agenda and show_agenda %}
{# Note: if called with show_agenda=True, calling template must load agenda_materials.js, needed by session_agenda_include.html #}
{% include "meeting/session_agenda_include.html" with slug=slug session=session timeslot=timeslot only %}
<!-- agenda pop-up button -->
<a class="" data-toggle="modal" data-target="#modal-{{slug}}" title="Show meeting materials"><span class="fa fa-fw fa-arrows-alt"></span></a>

View file

@ -8,11 +8,12 @@
{% if session.name %} : {{ session.name }}{% endif %}
{% if not session.cancelled %}
<span class="regular pull-right">
{# see note in the included templates re: show_agenda parameter and required JS import #}
{% if meeting.type.slug == 'interim' %}
{% include "meeting/interim_session_buttons.html" with show_agenda=False show_empty=False %}
{% else %}
{% with schedule=meeting.schedule %}
{% include "meeting/session_buttons_include.html" %}
{% include "meeting/session_buttons_include.html" with show_agenda=False %}
{% endwith %}
{% endif %}
</span>

View file

@ -180,6 +180,7 @@
<script src="{% static 'moment/min/moment.min.js' %}"></script>
<script src="{% static 'moment-timezone/builds/moment-timezone-with-data-10-year-range.min.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_filter.js' %}"></script>
<script src="{% static 'ietf/js/agenda/agenda_materials.js' %}"></script>
<script src="{% static 'ietf/js/agenda/timezone.js' %}"></script>
<script>
// List of all events with meta-info needed for filtering
@ -338,37 +339,6 @@
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) {
var t = xhr.getResponseHeader("content-type");
if (t.indexOf("text/plain") > -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>";
}
$(i).html(data);
});
}
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/html") > -1) {
// nothing to do here
} else {
data = "<p>Unknown type: " + xhr.getResponseHeader("content-type") + "</p>";
}
$(j).html(data);
});
}
});
function format_session_time(session_elt, tz) {
var start = moment.utc($(session_elt).attr('data-start-utc'));
var end = moment.utc($(session_elt).attr('data-end-utc'));

View file

@ -78,3 +78,16 @@ class IetfSeleniumTestCase(IetfLiveServerTestCase):
actions = ActionChains(self.driver)
actions.move_to_element(element).perform()
class presence_of_element_child_by_css_selector:
"""Wait for presence of a child of a WebElement matching a CSS selector
This is a condition class for use with WebDriverWait.
"""
def __init__(self, element, child_selector):
self.element = element
self.child_selector = child_selector
def __call__(self, driver):
child = self.element.find_element_by_css_selector(self.child_selector)
return child if child is not None else False