feat: persist timeslot types made visible in the schedule editor (#3778)

This adds a POST action to the edit_meeting_schedule view that sets a list
of currently visible timeslot types in the user's session. On display of
the schedule editor, this is consulted before defaulting to show only 'regular'
timeslots. An ajax request is made to update the session when changing the
settings in the editor.
This commit is contained in:
Jennifer Richards 2022-04-05 16:00:59 -03:00 committed by GitHub
parent cb3177efb9
commit 016ee71855
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 32 deletions

View file

@ -3898,6 +3898,69 @@ class EditTests(TestCase):
self.assertTrue(mars_slot.slot_to_the_right)
self.assertTrue(mars_scheduled.slot_to_the_right)
def test_updateview(self):
"""The updateview action should set visible timeslot types in the session"""
meeting = MeetingFactory(type_id='ietf')
url = urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number})
types_to_enable = ['regular', 'reg', 'other']
r = self.client.post(
url,
{
'action': 'updateview',
'enabled_timeslot_types[]': types_to_enable,
},
)
self.assertEqual(r.status_code, 200)
session_data = self.client.session
self.assertIn('edit_meeting_schedule', session_data)
self.assertCountEqual(
session_data['edit_meeting_schedule']['enabled_timeslot_types'],
types_to_enable,
'Should set types requested',
)
r = self.client.post(
url,
{
'action': 'updateview',
'enabled_timeslot_types[]': types_to_enable + ['faketype'],
},
)
self.assertEqual(r.status_code, 200)
session_data = self.client.session
self.assertIn('edit_meeting_schedule', session_data)
self.assertCountEqual(
session_data['edit_meeting_schedule']['enabled_timeslot_types'],
types_to_enable,
'Should ignore unknown types',
)
def test_persistent_enabled_timeslot_types(self):
meeting = MeetingFactory(type_id='ietf')
TimeSlotFactory(meeting=meeting, type_id='other')
TimeSlotFactory(meeting=meeting, type_id='reg')
# test default behavior (only 'regular' enabled)
r = self.client.get(urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number}))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="regular"][checked]')), 1)
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="other"]:not([checked])')), 1)
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="reg"]:not([checked])')), 1)
# test with 'regular' and 'other' enabled via session store
client_session = self.client.session # must store as var, new session is created on access
client_session['edit_meeting_schedule'] = {
'enabled_timeslot_types': ['regular', 'other']
}
client_session.save()
r = self.client.get(urlreverse('ietf.meeting.views.edit_meeting_schedule', kwargs={'num': meeting.number}))
self.assertEqual(r.status_code, 200)
q = PyQuery(r.content)
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="regular"][checked]')), 1)
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="other"][checked]')), 1)
self.assertEqual(len(q('#timeslot-type-toggles-modal input[value="reg"]:not([checked])')), 1)
class SessionDetailsTests(TestCase):

View file

@ -728,10 +728,18 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
return JsonResponse(data, status=status)
if request.method == 'POST':
if not can_edit:
permission_denied(request, "Can't edit this schedule.")
action = request.POST.get('action')
if action == 'updateview':
# allow updateview action even if can_edit is false, it affects only the user's session
sess_data = request.session.setdefault('edit_meeting_schedule', {})
enabled_types = [ts_type.slug for ts_type in TimeSlotTypeName.objects.filter(
used=True,
slug__in=request.POST.getlist('enabled_timeslot_types[]', [])
)]
sess_data['enabled_timeslot_types'] = enabled_types
return _json_response(True)
elif not can_edit:
permission_denied(request, "Can't edit this schedule.")
# Handle ajax requests. Most of these return JSON responses with at least a 'success' key.
# For the swapdays and swaptimeslots actions, the response is either a redirect to the
@ -949,6 +957,16 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
key=lambda tstype: tstype.name,
)
# extract view configuration from session store
session_data = request.session.get('edit_meeting_schedule', None)
if session_data is None:
enabled_timeslot_types = ['regular']
else:
enabled_timeslot_types = [
ts_type.slug for ts_type in timeslot_types
if ts_type.slug in session_data.get('enabled_timeslot_types', [])
]
return render(request, "meeting/edit_meeting_schedule.html", {
'meeting': meeting,
'schedule': schedule,
@ -963,6 +981,7 @@ def edit_meeting_schedule(request, num=None, owner=None, name=None):
'timeslot_types': timeslot_types,
'hide_menu': True,
'lock_time': lock_time,
'enabled_timeslot_types': enabled_timeslot_types,
})

View file

@ -19,6 +19,17 @@ $(function () {
alert("Error: " + errorText);
}
function ajaxCall(action, data) {
const ajaxData = { action: action };
Object.assign(ajaxData, data);
return jQuery.ajax({
url: window.location.href,
method: "post",
timeout: 5 * 1000,
data: ajaxData,
});
}
/**
* Time to treat as current time for computing whether to lock timeslots
* @returns {*} Moment object equal to lockSeconds in the future
@ -437,26 +448,20 @@ $(function () {
}
if (dropParent.hasClass("unassigned-sessions")) {
jQuery.ajax({
url: window.location.href,
method: "post",
timeout: 5 * 1000,
data: {
action: "unassign",
session: sessionElement.id.slice("session".length)
}
}).fail(failHandler).done(done);
ajaxCall(
"unassign",
{ session: sessionElement.id.slice('session'.length) }
).fail(failHandler)
.done(done);
} else {
jQuery.ajax({
url: window.location.href,
method: "post",
data: {
action: "assign",
ajaxCall(
"assign",
{
session: sessionElement.id.slice("session".length),
timeslot: dropParent.attr("id").slice("timeslot".length)
},
timeout: 5 * 1000
}).fail(failHandler).done(done);
}
).fail(failHandler)
.done(done);
}
});
@ -759,16 +764,20 @@ $(function () {
// Toggling timeslot types
function updateTimeSlotTypeToggling() {
let checked = [];
timeSlotTypeInputs.filter(":checked").each(function () {
checked.push("[data-type=" + this.value + "]");
});
const checkedTypes = jQuery.map(timeSlotTypeInputs.filter(":checked"), elt => elt.value);
const checkedSelectors = checkedTypes.map(t => '[data-type="' + t + '"]').join(",");
sessions.filter(checked.join(",")).removeClass('hidden-timeslot-type');
sessions.not(checked.join(",")).addClass('hidden-timeslot-type');
timeslots.filter(checked.join(",")).removeClass('hidden-timeslot-type');
timeslots.not(checked.join(",")).addClass('hidden-timeslot-type');
sessions.filter(checkedSelectors).removeClass('hidden-timeslot-type');
sessions.not(checkedSelectors).addClass('hidden-timeslot-type');
timeslots.filter(checkedSelectors).removeClass('hidden-timeslot-type');
timeslots.not(checkedSelectors).addClass('hidden-timeslot-type');
updateGridVisibility();
return checkedTypes;
}
function updateTimeSlotTypeTogglingAndSave() {
const checkedTypes = updateTimeSlotTypeToggling();
ajaxCall('updateview', {enabled_timeslot_types: checkedTypes});
}
// Toggling session purposes
@ -783,7 +792,7 @@ $(function () {
}
if (timeSlotTypeInputs.length > 0) {
timeSlotTypeInputs.on("change", updateTimeSlotTypeToggling);
timeSlotTypeInputs.on("change", updateTimeSlotTypeTogglingAndSave);
updateTimeSlotTypeToggling();
schedEditor.find('#timeslot-type-toggles-modal .timeslot-type-toggles .select-all')
.get(0)
@ -791,7 +800,7 @@ $(function () {
'click',
function() {
timeSlotTypeInputs.prop('checked', true);
updateTimeSlotTypeToggling();
updateTimeSlotTypeTogglingAndSave();
});
schedEditor.find('#timeslot-type-toggles-modal .timeslot-type-toggles .clear-all')
.get(0)
@ -799,7 +808,7 @@ $(function () {
'click',
function() {
timeSlotTypeInputs.prop('checked', false);
updateTimeSlotTypeToggling();
updateTimeSlotTypeTogglingAndSave();
});
}

View file

@ -312,7 +312,7 @@
<label class="timeslot-type-{{ type.slug }}">
<input type="checkbox"
class="form-check-input"
{% if type.slug == 'regular' %}checked{% endif %}
{% if type.slug in enabled_timeslot_types %}checked{% endif %}
value="{{ type.slug }}">
{{ type }}
</label>