canvas_webex 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +19 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +661 -0
- data/README.md +28 -0
- data/Rakefile +7 -0
- data/app/models/cisco_webex_conference.rb +102 -0
- data/app/views/plugins/_webex_settings.html.erb +34 -0
- data/canvas_webex.gemspec +26 -0
- data/lib/canvas/plugins/cisco_webex.rb +47 -0
- data/lib/canvas/plugins/validators/cisco_webex_validator.rb +77 -0
- data/lib/canvas_webex.rb +60 -0
- data/lib/canvas_webex/meeting.rb +84 -0
- data/lib/canvas_webex/service.rb +120 -0
- data/lib/canvas_webex/version.rb +22 -0
- data/spec/fixtures/create_meeting.xml +22 -0
- data/spec/fixtures/get_meeting.xml +129 -0
- data/spec/fixtures/host_meeting_url.xml +18 -0
- data/spec/fixtures/join_meeting_url.xml +21 -0
- data/spec/fixtures/meeting_list.xml +374 -0
- data/spec/fixtures/recording_list.xml +57 -0
- data/spec/lib/canvas_webex/meeting_spec.rb +31 -0
- data/spec/lib/canvas_webex/service_spec.rb +48 -0
- data/spec/spec_helper.rb +42 -0
- metadata +161 -0
data/README.md
ADDED
@@ -0,0 +1,28 @@
|
|
1
|
+
# CanvasWebEx
|
2
|
+
|
3
|
+
CanvasWebEx is an WebEx plugin for [Instructure
|
4
|
+
Canvas](http://instructure.com). It allows users to create and join Cisco
|
5
|
+
WebEx conferences directly from their courses.
|
6
|
+
|
7
|
+
## Installation
|
8
|
+
|
9
|
+
Add this line to your application's Gemfile:
|
10
|
+
|
11
|
+
gem 'canvas_webex'
|
12
|
+
|
13
|
+
## Usage
|
14
|
+
|
15
|
+
CanvasWebEx registers itself as a Canvas plugin and can be configured by
|
16
|
+
going to /plugins in your Canvas instance and entering your WebEx information.
|
17
|
+
|
18
|
+
CanvasWebEx assumes that it's being used inside of a Canvas instance, and
|
19
|
+
will explode in strange and beautiful ways if you try to run anything in it
|
20
|
+
outside of Canvas.
|
21
|
+
|
22
|
+
## Contributing
|
23
|
+
|
24
|
+
1. Fork it
|
25
|
+
2. Create your feature branch (`git checkout -b my-new-feature`)
|
26
|
+
3. Commit your changes (`git commit -am 'Add some feature'`)
|
27
|
+
4. Push to the branch (`git push origin my-new-feature`)
|
28
|
+
5. Create new Pull Request
|
data/Rakefile
ADDED
@@ -0,0 +1,102 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2013 Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#
|
18
|
+
|
19
|
+
class CiscoWebexConference < WebConference
|
20
|
+
|
21
|
+
# Public: Start a new conference and return its key. (required by WebConference)
|
22
|
+
#
|
23
|
+
# Returns a meeting.
|
24
|
+
def initiate_conference
|
25
|
+
unless self.conference_key.present?
|
26
|
+
webex_meeting = CanvasWebex::Meeting.create(self.title)
|
27
|
+
options = {}
|
28
|
+
options[:duration] = self.duration || 999999
|
29
|
+
self.conference_key = webex_meeting.meeting_key
|
30
|
+
save
|
31
|
+
end
|
32
|
+
self.conference_key
|
33
|
+
end
|
34
|
+
|
35
|
+
# Public: Determine the status of the conference (required by WebConference).
|
36
|
+
#
|
37
|
+
# Returns conference status as a symbol (either :active or :closed).
|
38
|
+
def conference_status
|
39
|
+
if meeting || self.started_at.nil?
|
40
|
+
:active
|
41
|
+
else
|
42
|
+
self.close unless self.ended_at
|
43
|
+
:closed
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
# Public: Add an admin to the conference and create a meeting URL (required by WebConference).
|
48
|
+
#
|
49
|
+
# admin - The user to add to the conference as an admin.
|
50
|
+
# _ - Included for compatibility w/ web_conference.rb
|
51
|
+
#
|
52
|
+
# Returns a meeting URL string.
|
53
|
+
def admin_join_url(admin, _ = nil)
|
54
|
+
if meeting
|
55
|
+
meeting.host_url
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Public: Add a participant to the conference and create a meeting URL.
|
60
|
+
# Make the user a conference admin if they have permissions to create
|
61
|
+
# a conference (required by WebConference).
|
62
|
+
#
|
63
|
+
# user - The user to add to the conference as an admin.
|
64
|
+
# _ - Included for compatibility w/ web_conference.rb
|
65
|
+
#
|
66
|
+
# Returns a meeting URL string.
|
67
|
+
def participant_join_url(user, _ = nil)
|
68
|
+
if meeting
|
69
|
+
if grants_right?(user, nil, :initiate)
|
70
|
+
meeting.host_url
|
71
|
+
else
|
72
|
+
meeting.join_url
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
# Public: List all of the recordings for a meeting
|
78
|
+
#
|
79
|
+
# Returns an Array of recording hashes, or an empty Array if there are no recordings
|
80
|
+
def recordings
|
81
|
+
CanvasWebex::Meeting.recordings(self.conference_key)
|
82
|
+
end
|
83
|
+
|
84
|
+
def after_find
|
85
|
+
conference_status
|
86
|
+
end
|
87
|
+
|
88
|
+
protected
|
89
|
+
|
90
|
+
def webex_client
|
91
|
+
CanvasWebex.client
|
92
|
+
end
|
93
|
+
|
94
|
+
# Protected: Retrieve the meeting if it is still active
|
95
|
+
#
|
96
|
+
# Returns the meeting object, or nil if the meeting is ended
|
97
|
+
def meeting
|
98
|
+
self.conference_key && CanvasWebex::Meeting.retrieve(self.conference_key)
|
99
|
+
end
|
100
|
+
|
101
|
+
|
102
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
<% fields_for :settings, OpenObject.new(settings) do |f| %>
|
2
|
+
<table class="formtable" style="width: 500px;">
|
3
|
+
<tbody>
|
4
|
+
<tr>
|
5
|
+
<td><%= f.blabel :webex_id, :en => "WebEx ID" %></td>
|
6
|
+
<td>
|
7
|
+
<%= f.text_field :webex_id, :autocomplete => false %><br>
|
8
|
+
</td>
|
9
|
+
</tr>
|
10
|
+
<tr>
|
11
|
+
<td><%= f.blabel :password, :en => "Password" %></td>
|
12
|
+
<td>
|
13
|
+
<%= f.password_field :password, :autocomplete => false %><br>
|
14
|
+
</td>
|
15
|
+
</tr>
|
16
|
+
<tr>
|
17
|
+
<td><%= f.blabel :site_id, "Site ID" %></td>
|
18
|
+
<td>
|
19
|
+
<%= f.text_field :site_id, :autocomplete => false %><br>
|
20
|
+
</td>
|
21
|
+
</tr>
|
22
|
+
<tr>
|
23
|
+
<td><%= f.blabel :site_name, "Site Name" %></td>
|
24
|
+
<td>
|
25
|
+
<%= f.text_field :site_name, :autocomplete => false %><br>
|
26
|
+
<small class="help-text">
|
27
|
+
<%= t(:site_name_description, "Site name for your WebEx account,
|
28
|
+
if the url is \"https://school.webex.com\" then your site name is 'school'") %>
|
29
|
+
</small>
|
30
|
+
</td>
|
31
|
+
</tr>
|
32
|
+
</tbody>
|
33
|
+
</table>
|
34
|
+
<% end %>
|
@@ -0,0 +1,26 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
lib = File.expand_path('../lib', __FILE__)
|
3
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
4
|
+
require 'canvas_webex/version'
|
5
|
+
|
6
|
+
Gem::Specification.new do |gem|
|
7
|
+
gem.name = 'canvas_webex'
|
8
|
+
gem.version = CanvasWebex::VERSION
|
9
|
+
gem.authors = ['Nathan Mills']
|
10
|
+
gem.email = ['nathanm@instructure.com']
|
11
|
+
gem.description = %q{Canvas WebEx is an Cisco Webex plugin for the Instructure Canvas LMS. It allows teachers and administrators to create and launch WEbEx conferences directly from their courses.}
|
12
|
+
gem.summary = %q{Cisco WebEx integration for Instructure Canvas (http://instructure.com).}
|
13
|
+
gem.homepage = 'http://instructure.com'
|
14
|
+
|
15
|
+
gem.files = `git ls-files`.split($/)
|
16
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
17
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
18
|
+
gem.require_paths = %w{app lib}
|
19
|
+
|
20
|
+
gem.add_development_dependency 'rake'
|
21
|
+
gem.add_development_dependency 'rspec'
|
22
|
+
gem.add_development_dependency 'nokogiri'
|
23
|
+
gem.add_development_dependency 'webmock'
|
24
|
+
gem.add_development_dependency 'pry'
|
25
|
+
end
|
26
|
+
|
@@ -0,0 +1,47 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2012 Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Canvas
|
20
|
+
module Plugins
|
21
|
+
class CiscoWebex
|
22
|
+
|
23
|
+
# Public: Bootstrap the gem on app load.
|
24
|
+
#
|
25
|
+
# Returns nothing.
|
26
|
+
def initialize; register; end
|
27
|
+
|
28
|
+
protected
|
29
|
+
# Internal: Register as a plugin with Canvas
|
30
|
+
#
|
31
|
+
# Returns a Canvas plugin object
|
32
|
+
def register
|
33
|
+
Canvas::Plugin.register('cisco_webex', :web_conferencing, {
|
34
|
+
:name => lambda { t(:name, 'Cisco WebEx') },
|
35
|
+
:description => lambda { t(:description, 'Cisco WebEx web conferencing support.') },
|
36
|
+
:author => 'Instructure',
|
37
|
+
:author_website => 'http://instructure.com',
|
38
|
+
:version => CanvasWebex::VERSION,
|
39
|
+
:settings_partial => 'plugins/webex_settings',
|
40
|
+
:validator => 'CiscoWebexValidator',
|
41
|
+
:encrypted_settings => [:password]
|
42
|
+
})
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2013 Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#
|
18
|
+
|
19
|
+
module Canvas
|
20
|
+
module Plugins
|
21
|
+
module Validators
|
22
|
+
class CiscoWebexValidator
|
23
|
+
# Public: An array of allowed plugin settings.
|
24
|
+
REQUIRED_KEYS = %w{webex_id password site_id site_name}
|
25
|
+
|
26
|
+
# Public: Validate setting input for this plugin.
|
27
|
+
#
|
28
|
+
# settings - A hash of settings params.
|
29
|
+
# plugin_setting - A plugin setting object.
|
30
|
+
#
|
31
|
+
# Returns false on error or a hash of settings options.
|
32
|
+
def self.validate(settings, plugin_setting)
|
33
|
+
filtered_settings = settings.slice(*REQUIRED_KEYS)
|
34
|
+
if all_empty?(filtered_settings)
|
35
|
+
# Allow no settings.
|
36
|
+
{}
|
37
|
+
else
|
38
|
+
if valid?(filtered_settings)
|
39
|
+
filtered_settings
|
40
|
+
else
|
41
|
+
plugin_setting.errors.add_to_base(I18n.t('canvas.plugins.errors.all_fields_required', 'All fields are required'))
|
42
|
+
false
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
protected
|
48
|
+
# Internal: Determine if a given settings hash is empty.
|
49
|
+
#
|
50
|
+
# settings - A hash of setting params.
|
51
|
+
#
|
52
|
+
# Returns a boolean.
|
53
|
+
def self.all_empty?(settings)
|
54
|
+
settings.values.all?(&:blank?)
|
55
|
+
end
|
56
|
+
|
57
|
+
# Internal: Determine if any settings are missing from the given hash.
|
58
|
+
#
|
59
|
+
# settings - A hash of setting params.
|
60
|
+
#
|
61
|
+
# Returns a boolean.
|
62
|
+
def self.any_empty?(settings)
|
63
|
+
settings.values.any?(&:blank?)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Internal: Validate that all required settings are present.
|
67
|
+
#
|
68
|
+
# settings - The hash of settings to validate.
|
69
|
+
#
|
70
|
+
# Returns boolean.
|
71
|
+
def self.valid?(settings)
|
72
|
+
!(any_empty?(settings) || (REQUIRED_KEYS & settings.keys) != REQUIRED_KEYS)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
data/lib/canvas_webex.rb
ADDED
@@ -0,0 +1,60 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2013 Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#
|
18
|
+
|
19
|
+
require "canvas_webex/version"
|
20
|
+
require "canvas/plugins/validators/cisco_webex_validator"
|
21
|
+
require "canvas/plugins/cisco_webex"
|
22
|
+
require "canvas_webex/service"
|
23
|
+
require "canvas_webex/meeting"
|
24
|
+
|
25
|
+
module CanvasWebex
|
26
|
+
class ConnectionError < StandardError; end
|
27
|
+
|
28
|
+
# Public: Configure as a Canvas plugin
|
29
|
+
#
|
30
|
+
# Returns nothing.
|
31
|
+
def self.register
|
32
|
+
Rails.configuration.to_prepare do
|
33
|
+
view_path = File.join(File.dirname(__FILE__), '..', 'app', 'views')
|
34
|
+
unless ApplicationController.view_paths.include?(view_path)
|
35
|
+
ApplicationController.view_paths.unshift(view_path)
|
36
|
+
end
|
37
|
+
|
38
|
+
require_dependency "models/cisco_webex_conference"
|
39
|
+
|
40
|
+
Canvas::Plugins::CiscoWebex.new
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Find the plugin configuration.
|
45
|
+
#
|
46
|
+
# Returns a settings hash.
|
47
|
+
def self.config
|
48
|
+
Canvas::Plugin.find('cisco_webex').settings || {}
|
49
|
+
end
|
50
|
+
|
51
|
+
# Return a cached Connect Service object to make requests with.
|
52
|
+
#
|
53
|
+
# Returns a CiscoWwebex::Service.
|
54
|
+
def self.client
|
55
|
+
@client ||= Service.new(*self.config.values_at(:webex_id, :password_dec, :site_id, :site_name, :partner_id))
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
59
|
+
|
60
|
+
CanvasWebex.register
|
@@ -0,0 +1,84 @@
|
|
1
|
+
#
|
2
|
+
# Copyright (C) 2013 Instructure, Inc.
|
3
|
+
#
|
4
|
+
# This file is part of Canvas.
|
5
|
+
#
|
6
|
+
# Canvas is free software: you can redistribute it and/or modify it under
|
7
|
+
# the terms of the GNU Affero General Public License as published by the Free
|
8
|
+
# Software Foundation, version 3 of the License.
|
9
|
+
#
|
10
|
+
# Canvas is distributed in the hope that it will be useful, but WITHOUT ANY
|
11
|
+
# WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
|
12
|
+
# A PARTICULAR PURPOSE. See the GNU Affero General Public License for more
|
13
|
+
# details.
|
14
|
+
#
|
15
|
+
# You should have received a copy of the GNU Affero General Public License along
|
16
|
+
# with this program. If not, see <http://www.gnu.org/licenses/>.
|
17
|
+
#
|
18
|
+
module CanvasWebex
|
19
|
+
class Meeting
|
20
|
+
|
21
|
+
ATTRIBUTES = [:meeting_key, :conf_name, :start_date, :host_joined, :status]
|
22
|
+
|
23
|
+
def self.retrieve(meeting_key, client = CanvasWebex.client)
|
24
|
+
if response = client.get_meeting(meeting_key)
|
25
|
+
Meeting.new(Nokogiri::XML(response.to_xml))
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def self.create(meeting_name, client = CanvasWebex.client)
|
30
|
+
if response = client.create_meeting(meeting_name)
|
31
|
+
if meeting_key = response.at_xpath('//meetingkey').try(:text)
|
32
|
+
retrieve(meeting_key, client)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
def host_url(client = CanvasWebex.client)
|
38
|
+
if response = client.host_meeting_url(meeting_key, nil)
|
39
|
+
response.at_xpath('hostMeetingURL').try(:text)
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
def join_url(client = CanvasWebex.client)
|
44
|
+
if response = client.join_meeting_url(meeting_key, nil)
|
45
|
+
response.at_xpath('joinMeetingURL').text
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.recordings(meeting_key, client = CanvasWebex.client)
|
50
|
+
if response = meeting_key && client.list_recordings(meeting_key)
|
51
|
+
response.search('recording').map do |rec_xml|
|
52
|
+
recording = {
|
53
|
+
recording_id: rec_xml.at_xpath('recordingID').try(:text),
|
54
|
+
title: rec_xml.at_xpath('name').try(:text),
|
55
|
+
playback_url: rec_xml.at_xpath('streamURL').try(:text)
|
56
|
+
}
|
57
|
+
if duration = rec_xml.at_xpath('duration').try(:text)
|
58
|
+
recording[:duration_minutes] = duration.to_i / 60
|
59
|
+
end
|
60
|
+
recording
|
61
|
+
end
|
62
|
+
else
|
63
|
+
[]
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
# Public: Create a new Meeting
|
68
|
+
#
|
69
|
+
# meeting_xml - The xml returned for a meeting (must already exist).
|
70
|
+
def initialize(meeting_xml)
|
71
|
+
@attr_cache = {}
|
72
|
+
@xml = meeting_xml
|
73
|
+
end
|
74
|
+
|
75
|
+
def method_missing(meth, *args, &block)
|
76
|
+
if ATTRIBUTES.include?(meth)
|
77
|
+
@attr_cache[meth] ||= @xml.at_xpath("//*[contains(translate(name(),'ABCDEFGHIJKLMNOPQRSTUVWXYZ','abcdefghijklmnopqrstuvwxyz'), '#{meth.to_s.camelcase(:lower).downcase}')]").try(:text)
|
78
|
+
else
|
79
|
+
super
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
end
|
84
|
+
end
|