Skip to content

Commit 0ade3f7

Browse files
Handle groups without area when sorting unscheduled sessions in schedule editor. Fixes ietf-tools#3173. Commit ready for merge.
- Legacy-Id: 19073
1 parent bfad845 commit 0ade3f7

2 files changed

Lines changed: 144 additions & 13 deletions

File tree

ietf/meeting/tests_js.py

Lines changed: 133 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@
2424
from ietf.person.models import Person
2525
from ietf.group.models import Group
2626
from ietf.group.factories import GroupFactory
27-
from ietf.meeting.factories import SessionFactory, TimeSlotFactory
27+
from ietf.meeting.factories import MeetingFactory, SessionFactory, TimeSlotFactory
2828
from ietf.meeting.test_data import make_meeting_test_data, make_interim_meeting
2929
from ietf.meeting.models import (Schedule, SchedTimeSessAssignment, Session,
3030
Room, TimeSlot, Constraint, ConstraintName,
@@ -39,7 +39,7 @@
3939
from selenium.webdriver.common.by import By
4040
from selenium.webdriver.support.ui import WebDriverWait
4141
from selenium.webdriver.support import expected_conditions
42-
from selenium.common.exceptions import NoSuchElementException
42+
from selenium.common.exceptions import NoSuchElementException, TimeoutException
4343

4444

4545
@ifSeleniumEnabled
@@ -225,6 +225,137 @@ def test_edit_meeting_schedule(self):
225225

226226
self.assertTrue(self.driver.find_elements_by_css_selector('#timeslot{} #session{}'.format(slot4.pk, s1.pk)))
227227

228+
def test_unassigned_sessions_sort(self):
229+
"""Unassigned session sorting should behave correctly
230+
231+
Sorting options and list of sort criteria
232+
name (name, duration, id)
233+
parent (parent, name, duration, id)
234+
duration (duration, parent, name, id)
235+
comments (presence of comments, parent, name, duration, id)
236+
"""
237+
# Define helpers
238+
def sort_by_position(driver, sessions):
239+
"""Helper to sort sessions by the position of their session element in the unscheduled box"""
240+
def _sort_key(sess):
241+
elt = driver.find_element_by_id('session{}'.format(sess.pk))
242+
return (elt.location['y'], elt.location['x'])
243+
return sorted(sessions, key=_sort_key)
244+
245+
wait = WebDriverWait(self.driver, 2)
246+
247+
def wait_for_order(sessions, expected_order, fail_message):
248+
"""Helper to wait for sorting to complete"""
249+
try:
250+
wait.until(
251+
lambda driver: sort_by_position(driver, sessions) == expected_order,
252+
)
253+
except TimeoutException:
254+
pass # Fall through to the assertion which will fail, don't throw a confusing timeout exception
255+
self.assertEqual(sort_by_position(self.driver, sessions), expected_order, fail_message)
256+
257+
# Start the test here
258+
# set up several WGs in various areas, including no area.
259+
area_acronyms = ['A', 'B', 'C', 'D']
260+
areas = [GroupFactory(type_id='area', acronym=acro) for acro in area_acronyms]
261+
262+
# now create WGs with acronyms that sort differently than by area (g00, g01, g02...)
263+
num = 0
264+
wgs = []
265+
group_acro = lambda n: 'g{:02d}'.format(n)
266+
for _ in range(2):
267+
wgs.append(GroupFactory(acronym=group_acro(num), type_id='wg', parent=None))
268+
num += 1
269+
for area in areas:
270+
wgs.append(GroupFactory(acronym=group_acro(num), type_id='wg', parent=area))
271+
num += 1
272+
273+
# Create an IETF meeting...
274+
meeting = MeetingFactory(type_id='ietf')
275+
276+
# ...and sessions for the groups. Use durations that are in a different order than
277+
# area or name. The wgs list is in ascending acronym order, so use descending durations.
278+
sessions = []
279+
for n, wg in enumerate(wgs[::-1]):
280+
sessions.append(
281+
SessionFactory(
282+
meeting=meeting,
283+
group=wg,
284+
requested_duration=datetime.timedelta(minutes=30 + 5 * n),
285+
status_id='schedw',
286+
add_to_schedule=False,
287+
)
288+
)
289+
290+
# Finally, assign comments to some sessions. Assign every 3rd until we reach the end.
291+
# This should be a different sort than any of the other axes.
292+
for sess in sessions[::3]:
293+
sess.comments = 'special request'
294+
sess.save()
295+
296+
url = self.absreverse('ietf.meeting.views.edit_meeting_schedule', kwargs=dict(num=meeting.number))
297+
self.login('secretary')
298+
self.driver.get(url)
299+
300+
301+
select = self.driver.find_element_by_name('sort_unassigned')
302+
options = {
303+
opt.get_attribute('value'): opt
304+
for opt in select.find_elements_by_tag_name('option')
305+
}
306+
307+
# check sorting by name
308+
options['name'].click()
309+
self.assertEqual(select.get_attribute('value'), 'name')
310+
expected_order = sorted(
311+
sessions,
312+
key=lambda s: (
313+
s.group.acronym,
314+
s.requested_duration,
315+
)
316+
)
317+
wait_for_order(sessions, expected_order, 'Failed to sort by name')
318+
319+
# check sorting by parent
320+
options['parent'].click()
321+
self.assertEqual(select.get_attribute('value'), 'parent')
322+
expected_order = sorted(
323+
sessions,
324+
key=lambda s: (
325+
s.group.parent.acronym if s.group.parent else '',
326+
s.group.acronym,
327+
s.requested_duration,
328+
)
329+
)
330+
wait_for_order(sessions, expected_order, 'Failed to sort by parent')
331+
332+
# check sorting by duration
333+
options['duration'].click()
334+
self.assertEqual(select.get_attribute('value'), 'duration')
335+
expected_order = sorted(
336+
sessions,
337+
key=lambda s: (
338+
s.requested_duration,
339+
s.group.parent.acronym if s.group.parent else '',
340+
s.group.acronym,
341+
)
342+
)
343+
wait_for_order(sessions, expected_order, 'Failed to sort by duration')
344+
345+
# check sorting by comments
346+
options['comments'].click()
347+
self.assertEqual(select.get_attribute('value'), 'comments')
348+
expected_order = sorted(
349+
sessions,
350+
key=lambda s: (
351+
0 if len(s.comments) > 0 else 1,
352+
s.group.parent.acronym if s.group.parent else '',
353+
s.group.acronym,
354+
s.requested_duration,
355+
)
356+
)
357+
wait_for_order(sessions, expected_order, 'Failed to sort by comments')
358+
228359
def test_unassigned_sessions_drop_target_visible_when_empty(self):
229360
"""The drop target for unassigned sessions should not collapse to 0 size
230361

ietf/static/ietf/js/edit-meeting-schedule.js

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -385,11 +385,13 @@ jQuery(document).ready(function () {
385385
}
386386

387387
function extractName(e) {
388-
return e.querySelector(".session-label").innerHTML;
388+
let labelElement = e.querySelector(".session-label");
389+
return labelElement ? labelElement.innerHTML : '';
389390
}
390391

391392
function extractParent(e) {
392-
return e.querySelector(".session-parent").innerHTML;
393+
let parentElement = e.querySelector(".session-parent");
394+
return parentElement ? parentElement.innerHTML : '';
393395
}
394396

395397
function extractDuration(e) {
@@ -400,15 +402,13 @@ jQuery(document).ready(function () {
400402
return e.querySelector(".session-info .comments") ? 0 : 1;
401403
}
402404

403-
let keyFunctions = [];
404-
if (sortBy == "name")
405-
keyFunctions = [extractName, extractDuration, extractId];
406-
else if (sortBy == "parent")
407-
keyFunctions = [extractParent, extractName, extractDuration, extractId];
408-
else if (sortBy == "duration")
409-
keyFunctions = [extractDuration, extractParent, extractName, extractId];
410-
else if (sortBy == "comments")
411-
keyFunctions = [extractComments, extractParent, extractName, extractDuration, extractId];
405+
const keyFunctionMap = {
406+
name: [extractName, extractDuration, extractId],
407+
parent: [extractParent, extractName, extractDuration, extractId],
408+
duration: [extractDuration, extractParent, extractName, extractId],
409+
comments: [extractComments, extractParent, extractName, extractDuration, extractId]
410+
};
411+
let keyFunctions = keyFunctionMap[sortBy];
412412

413413
let unassignedSessionsContainer = content.find(".unassigned-sessions .drop-target");
414414

0 commit comments

Comments
 (0)