preload big blue button conference recording data
test plan: * have big blue button plugin enabled (may need to test in a production-like environment) * the index should display recording data just like before (but also hopefully load much faster when there are multiple conferences) closes #LA-716 Change-Id: I51e814996c02edebf7c38520d66891967eda0dd1 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/228972 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Mysti Lilla <mysti@instructure.com> QA-Review: Anju Reddy <areddy@instructure.com> Product-Review: James Williams <jamesw@instructure.com>
This commit is contained in:
parent
32c99a4044
commit
82acf24eaa
|
@ -174,11 +174,14 @@ class ConferencesController < ApplicationController
|
|||
def api_index(conferences)
|
||||
route = polymorphic_url([:api_v1, @context, :conferences])
|
||||
web_conferences = Api.paginate(conferences, self, route)
|
||||
preload_recordings(web_conferences)
|
||||
render json: api_conferences_json(web_conferences, @current_user, session)
|
||||
end
|
||||
protected :api_index
|
||||
|
||||
def web_index(conferences)
|
||||
conferences = conferences.to_a
|
||||
preload_recordings(conferences)
|
||||
@new_conferences, @concluded_conferences = conferences.partition { |conference|
|
||||
conference.ended_at.nil?
|
||||
}
|
||||
|
@ -408,4 +411,12 @@ class ConferencesController < ApplicationController
|
|||
params.require(:web_conference).
|
||||
permit(:title, :duration, :description, :conference_type, :user_settings => strong_anything)
|
||||
end
|
||||
|
||||
def preload_recordings(conferences)
|
||||
conferences.group_by(&:class).each do |klass, klass_conferences|
|
||||
if klass.respond_to?(:preload_recordings) # should only be BigBlueButton for now
|
||||
klass.preload_recordings(klass_conferences)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -134,6 +134,29 @@ class BigBlueButtonConference < WebConference
|
|||
super
|
||||
end
|
||||
|
||||
attr_writer :loaded_recordings
|
||||
# we can use the same API method with multiple meeting ids to load all the recordings up in one go
|
||||
# instead of making a bunch of individual calls
|
||||
def self.preload_recordings(conferences)
|
||||
filtered_conferences = conferences.select{|c| c.conference_key && c.settings[:record]}
|
||||
return unless filtered_conferences.any?
|
||||
|
||||
# have a limit so we don't send a ridiculously long URL over
|
||||
limit = Setting.get("big_blue_button_preloaded_recordings_limit", "50").to_i
|
||||
filtered_conferences.each_slice(limit) do |sliced_conferences|
|
||||
meeting_ids = sliced_conferences.map(&:conference_key).join(",")
|
||||
response = send_request(:getRecordings, {
|
||||
:meetingID => meeting_ids,
|
||||
})
|
||||
result = response[:recordings] if response
|
||||
result = [] if result.is_a?(String)
|
||||
grouped_result = Array(result).group_by{|r| r[:meetingID]}
|
||||
sliced_conferences.each do |c|
|
||||
c.loaded_recordings = grouped_result[c.conference_key] || []
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def retouch?
|
||||
|
@ -167,22 +190,35 @@ class BigBlueButtonConference < WebConference
|
|||
end
|
||||
|
||||
def fetch_recordings
|
||||
return [] unless conference_key && settings[:record]
|
||||
@loaded_recordings ||= begin
|
||||
if conference_key && settings[:record]
|
||||
response = send_request(:getRecordings, {
|
||||
:meetingID => conference_key,
|
||||
})
|
||||
result = response[:recordings] if response
|
||||
result = [] if result.is_a?(String)
|
||||
Array(result)
|
||||
else
|
||||
[]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def generate_request(action, options)
|
||||
def generate_request(*args)
|
||||
self.class.generate_request(*args)
|
||||
end
|
||||
|
||||
def self.generate_request(action, options)
|
||||
query_string = options.to_query
|
||||
query_string << ("&checksum=" + Digest::SHA1.hexdigest(action.to_s + query_string + config[:secret_dec]))
|
||||
"https://#{config[:domain]}/bigbluebutton/api/#{action}?#{query_string}"
|
||||
end
|
||||
|
||||
def send_request(action, options)
|
||||
def send_request(*args)
|
||||
self.class.send_request(*args)
|
||||
end
|
||||
|
||||
def self.send_request(action, options)
|
||||
url_str = generate_request(action, options)
|
||||
http_response = nil
|
||||
Canvas.timeout_protection("big_blue_button") do
|
||||
|
@ -207,13 +243,13 @@ class BigBlueButtonConference < WebConference
|
|||
nil
|
||||
end
|
||||
|
||||
def xml_to_hash(xml_string)
|
||||
def self.xml_to_hash(xml_string)
|
||||
doc = Nokogiri::XML(xml_string)
|
||||
# assumes the top level value will be a hash
|
||||
xml_to_value(doc.root)
|
||||
end
|
||||
|
||||
def xml_to_value(node)
|
||||
def self.xml_to_value(node)
|
||||
child_elements = node.element_children
|
||||
|
||||
# if there are no children at all, then this is an empty node
|
||||
|
|
|
@ -97,6 +97,23 @@ describe ConferencesController do
|
|||
get 'index', params: {:course_id => @course.id}
|
||||
expect(assigns[:new_conferences]).to be_empty
|
||||
end
|
||||
|
||||
it "should preload recordings for BBB conferences" do
|
||||
PluginSetting.create!(name: 'big_blue_button',
|
||||
:settings => {
|
||||
:domain => "bbb.totallyanexampleplzdontcallthis.com",
|
||||
:secret_dec => "secret",
|
||||
})
|
||||
allow(BigBlueButtonConference).to receive(:send_request).and_return('')
|
||||
|
||||
user_session(@teacher)
|
||||
@bbb = BigBlueButtonConference.create!(:title => "my conference", :user => @teacher, :context => @course)
|
||||
@other = @course.web_conferences.create!(:conference_type => 'Wimba', :duration => 60, :user => @teacher)
|
||||
|
||||
expect(BigBlueButtonConference).to receive(:preload_recordings).with([@bbb])
|
||||
get 'index', params: {:course_id => @course.id}
|
||||
expect(response).to be_success
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST 'create'" do
|
||||
|
|
|
@ -0,0 +1,120 @@
|
|||
{
|
||||
"returncode": "SUCCESS",
|
||||
"recordings":
|
||||
[
|
||||
{
|
||||
"recordID": "somerecordingidformeeting1a",
|
||||
"meetingID": "instructure_web_conference_somemeetingkey1",
|
||||
"name": "Conference Development 101",
|
||||
"published": "true",
|
||||
"protected": "false",
|
||||
"startTime": "1513031612000",
|
||||
"endTime": "1513031628000",
|
||||
"metadata": {
|
||||
"isBreakout": "false"
|
||||
},
|
||||
"playback":
|
||||
[
|
||||
{
|
||||
"type": "statistics",
|
||||
"url": "https://bbb.blah.com/instructure/ae094b1eb62b634c377fc255149de61a04ee8787-1521832370987/statistics/",
|
||||
"length": null
|
||||
},
|
||||
{
|
||||
"type": "presentation",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031612456/presentation/",
|
||||
"length": "2",
|
||||
"preview": {
|
||||
"images":
|
||||
[
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031612456/presentation/thumbnails/thumb-1.png",
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031612456/presentation/thumbnails/thumb-2.png",
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031612456/presentation/thumbnails/thumb-3.png"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "video",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031612456/capture/",
|
||||
"length": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordID": "somerecordingidformeeting1b",
|
||||
"meetingID": "instructure_web_conference_somemeetingkey1",
|
||||
"name": "Conference Development 101",
|
||||
"published": "true",
|
||||
"protected": "false",
|
||||
"startTime": "1513031142000",
|
||||
"endTime": "1513031164000",
|
||||
"metadata": {
|
||||
"isBreakout": "false"
|
||||
},
|
||||
"playback":
|
||||
[
|
||||
{
|
||||
"type": "statistics",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/statistics/",
|
||||
"length": null
|
||||
},
|
||||
{
|
||||
"type": "presentation",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/",
|
||||
"length": "2",
|
||||
"preview": {
|
||||
"images":
|
||||
[
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/thumbnails/thumb-1.png",
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/thumbnails/thumb-2.png",
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/thumbnails/thumb-3.png"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "video",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/capture/",
|
||||
"length": "2"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"recordID": "somerecordingidformeeting2",
|
||||
"meetingID": "instructure_web_conference_somemeetingkey2",
|
||||
"name": "Conference Development 101",
|
||||
"published": "true",
|
||||
"protected": "false",
|
||||
"startTime": "1513031142000",
|
||||
"endTime": "1513031164000",
|
||||
"metadata": {
|
||||
"isBreakout": "false"
|
||||
},
|
||||
"playback":
|
||||
[
|
||||
{
|
||||
"type": "statistics",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/statistics/",
|
||||
"length": null
|
||||
},
|
||||
{
|
||||
"type": "presentation",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/",
|
||||
"length": "2",
|
||||
"preview": {
|
||||
"images":
|
||||
[
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/thumbnails/thumb-1.png",
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/thumbnails/thumb-2.png",
|
||||
"https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/presentation/thumbnails/thumb-3.png"
|
||||
]
|
||||
}
|
||||
},
|
||||
{
|
||||
"type": "video",
|
||||
"url": "https://bbb.blah.com/instructure/18974fe54920ac60ba913e34f49e4a9dabfeea2c-1513031142256/capture/",
|
||||
"length": "2"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
|
@ -107,6 +107,7 @@ describe BigBlueButtonConference do
|
|||
|
||||
describe 'plugin setting recording_enabled is enabled' do
|
||||
let(:get_recordings_fixture){File.read(Rails.root.join('spec', 'fixtures', 'files', 'conferences', 'big_blue_button_get_recordings_two.json'))}
|
||||
let(:get_recordings_bulk_fixture){File.read(Rails.root.join('spec', 'fixtures', 'files', 'conferences', 'big_blue_button_get_recordings_bulk.json'))}
|
||||
|
||||
before do
|
||||
allow(WebConference).to receive(:plugins).and_return([
|
||||
|
@ -218,6 +219,22 @@ describe BigBlueButtonConference do
|
|||
expect(response[:deleted]).to eq true
|
||||
end
|
||||
end
|
||||
|
||||
describe "recording preloading" do
|
||||
it "should load up all recordings in a single api call" do
|
||||
@bbb2 = BigBlueButtonConference.create!(:context => @bbb.context, :user => @bbb.user, :user_settings => @bbb.user_settings)
|
||||
allow(@bbb).to receive(:conference_key).and_return('instructure_web_conference_somemeetingkey1')
|
||||
allow(@bbb2).to receive(:conference_key).and_return('instructure_web_conference_somemeetingkey2')
|
||||
|
||||
response = JSON.parse(get_recordings_bulk_fixture, {symbolize_names: true})
|
||||
allow(BigBlueButtonConference).to receive(:send_request).and_return(response)
|
||||
|
||||
BigBlueButtonConference.preload_recordings([@bbb, @bbb2])
|
||||
[@bbb, @bbb2].each{|c| expect(c).to_not receive(:send_request)} # shouldn't need to send individual requests anymore
|
||||
expect(@bbb.recordings.map{|r| r[:recording_id]}).to match_array(["somerecordingidformeeting1a", "somerecordingidformeeting1b"])
|
||||
expect(@bbb2.recordings.map{|r| r[:recording_id]}).to match_array(["somerecordingidformeeting2"])
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe 'plugin setting recording disabled' do
|
||||
|
|
Loading…
Reference in New Issue