render calendar events as paginated data is fetched

fixes PFS-3691

note that the event source caching does not update as pages are fetched
 so switching between calendar pages while pages are fetching clears any
 already loaded events and effectively reverts to unpaginated rendering.

the changes to fullcalendar are unfortunate but if they are removed (in
 an upgrade, for example) then the calendar will revert to unpaginated
 rendering. also, the api is unchanged for anyone who does not want to
 use paginated rendering.

test plan
- have enough calendar events to have multiple pages of them.
 default page size is 50, but you can set per_page in the
 render_events_for_user Api.paginate call to lower that for
 testing
- view the calendar
- ensure that events are rendered as the pages are fetched. you
 can add a sleep statement to the calendar_events_api index action
 to make page loads more obvious for testing.
- regression test calendar

Change-Id: I758dfc0eda7f3ec93bcf1b9401b1024e8a8c6303
Reviewed-on: https://gerrit.instructure.com/74029
Reviewed-by: Kacey Roberts <kroberts@instructure.com>
Reviewed-by: Felix Milea-Ciobanu <fmileaciobanu@instructure.com>
Tested-by: Jenkins
QA-Review: Heath Hales <hhales@instructure.com>
Product-Review: Allison Weiss <allison@instructure.com>
This commit is contained in:
Joel Hough 2016-04-20 16:05:39 -06:00
parent dabd3407e1
commit 1415d7bc7d
6 changed files with 39 additions and 11 deletions

View File

@ -173,7 +173,7 @@ define [
@gotoDate(fcUtil.now())
# FullCalendar callbacks
getEvents: (start, end, timezone, cb) =>
getEvents: (start, end, timezone, donecb, datacb) =>
@dataSource.getEvents start, end, @visibleContextList, (events) =>
if @displayAppointmentEvents
@dataSource.getEventsForAppointmentGroup @displayAppointmentEvents, (aEvents) =>
@ -185,9 +185,14 @@ define [
event.removeClass('current-appointment-group')
for event in aEvents
event.addClass('current-appointment-group')
cb(calendarEventFilter(@displayAppointmentEvents, events.concat(aEvents)))
donecb(calendarEventFilter(@displayAppointmentEvents, events.concat(aEvents)))
else
cb(calendarEventFilter(@displayAppointmentEvents, events))
if (datacb?)
donecb([])
else
donecb(calendarEventFilter(@displayAppointmentEvents, events))
, datacb? && (events) =>
datacb(calendarEventFilter(@displayAppointmentEvents, events))
# Close all event details popup on the page and have them cleaned up.
closeEventPopups: ->

View File

@ -220,7 +220,7 @@ define [
params = { include: [ 'reserved_times', 'participant_count', 'appointments', 'child_events' ]}
@startFetch [[ group.url, params ]], dataCB, (() => cb @cache.appointmentGroups[group.id].appointmentEvents)
getEvents: (start, end, contexts, cb, options = {}) =>
getEvents: (start, end, contexts, donecb, datacb, options = {}) =>
if @inFlightRequest['default']
@pendingRequests.push([@getEvents, arguments, 'default'])
return
@ -267,20 +267,25 @@ define [
# Yay, this request can be satisfied by the cache
list = @getEventsFromCache(start, end, contexts)
list.requestID = options.requestID
cb list
datacb list if datacb?
donecb list
@processNextRequest()
return
requestResults = {}
dataCB = (data, url, params) =>
return unless data
newEvents = []
key = 'type_'+params.type
requestResult = requestResults[key] or {events: []}
requestResult.next = data.next
for e in data
event = commonEventFactory(e, @contexts)
if event && event.object.workflow_state != 'deleted'
newEvents.push(event)
requestResult.events.push(event)
newEvents.requestID = options.requestID
datacb newEvents if datacb?
requestResults[key] = requestResult
doneCB = () =>
@ -324,7 +329,7 @@ define [
list = @getEventsFromCache(start, end, contexts)
list.nextPageDate = nextPageDate
list.requestID = options.requestID
cb list
donecb list
@startFetch [
[ '/api/v1/calendar_events', params ]

View File

@ -34,7 +34,7 @@ define [
"CommonEvent/eventSaved" : @eventSaved
)
getEvents: (start, end, timezone, cb) =>
getEvents: (start, end, timezone, donecb, datacb) =>
# Since we have caching (lazyFetching) turned off, we can rely on this
# getting called every time we switch views, *before* the events are rendered.
# That makes this a great place to clear out the previous classes.
@ -42,7 +42,7 @@ define [
@calendar.find(".fc-widget-content td")
.removeClass("event slot-available")
.removeAttr('title')
@mainCalendar.getEvents start, end, timezone, cb
@mainCalendar.getEvents start, end, timezone, donecb, datacb
dayClick: (date) =>
@mainCalendar.gotoDate(date)

View File

@ -62,7 +62,7 @@ define [
_fetch: (start, callback) ->
end = fcUtil.clone(start).year(3000)
@lastRequestID = $.guid++
@dataSource.getEvents start, end, @contexts, callback, {singlePage: true, requestID: @lastRequestID}
@dataSource.getEvents start, end, @contexts, callback, undefined, {singlePage: true, requestID: @lastRequestID}
refetch: =>
return unless @startDate

View File

@ -9366,11 +9366,17 @@ function EventManager(options) { // assumed to be a calendar
reportEvents(cache);
}
}
}, function(eventInputs) {
if (fetchID == currentFetchID) {
for (i = 0; i < eventInputs.length; i++) {
renderEvent(eventInputs[i]);
}
}
});
}
function _fetchEventSource(source, callback) {
function _fetchEventSource(source, callback, dataCallback) {
var i;
var fetchers = fc.sourceFetchers;
var res;
@ -9408,7 +9414,8 @@ function EventManager(options) { // assumed to be a calendar
function(events) {
callback(events);
t.popLoading();
}
},
dataCallback
);
}
else if ($.isArray(events)) {

View File

@ -151,3 +151,14 @@ define [
equal list.length, 2
ok ['calendar_event_1', 'assignment_3'].indexOf(list[0].id) >= 0
ok ['calendar_event_1', 'assignment_3'].indexOf(list[1].id) >= 0
test 'pagination: calls data callback with each page of data if set', ->
@server.addCalendarEvent('course_1', '1', fcUtil.unwrap(@date1).toISOString())
@server.addAssignment('course_2', '3', fcUtil.unwrap(@date3).toISOString())
pages = 0
@source.getEvents @date1, @date4, @contexts, (list) ->
equal list.length, 2
equal pages, 2
, (list) ->
pages += 1
equal list.length, 1