xml config for blti extensions
test plan: - copy xml from http://lti-examples.heroku.com/config/editor_button - go to course settings and start creating a new tool - choose xml paste, and paste xml - save the tool - confirm the tool says "Editor button configured" - load a WYSIWYG in the course, confirm the new editor button appears - do the same thing but by URL instead of XML paste - confirm that normal tool configurations still work correctly Change-Id: I6bb53bde1986e9dda40488018e167bb626907453 Reviewed-on: https://gerrit.instructure.com/7527 Tested-by: Hudson <hudson@instructure.com> Reviewed-by: Brian Palmer <brianp@instructure.com>
This commit is contained in:
parent
e08b0d24a2
commit
b02a09c626
|
@ -216,13 +216,15 @@ class ExternalToolsController < ApplicationController
|
|||
# @argument resource_selection[url] [string] [optional] The url of the external tool
|
||||
# @argument resource_selection[selection_width] [string] [optional] The width of the dialog the tool is launched in
|
||||
# @argument resource_selection[selection_height] [string] [optional] The height of the dialog the tool is launched in
|
||||
# @argument config_type [string] [optional] Configuration can be passed in as CC xml instead of using query parameters. If this value is "by_url" or "by_xml" then an xml configuration will be expected in either the "config_xml" or "config_url" parameter. Note that the name parameter overrides the tool name provided in the xml
|
||||
# @argument config_xml [string] [optional] XML tool configuration, as specified in the CC xml specification. This is required if "config_type" is set to "by_xml"
|
||||
# @argument config_url [string] [optional] URL where the server can retrieve an XML tool configuration, as specified in the CC xml specification. This is required if "config_type" is set to "by_url"
|
||||
#
|
||||
# @example_request
|
||||
#
|
||||
# This would create a tool on this course with two custom fields and a course navigation tab
|
||||
# curl 'http://<canvas>/api/v1/courses/<course_id>/external_tools' \
|
||||
# -u '<username>:<password>' \
|
||||
# -F 'api_key=<key>' \
|
||||
# -F 'access_token=<token>' \
|
||||
# -F 'name=LTI Example' \
|
||||
# -F 'consumer_key=asdfg' \
|
||||
# -F 'shared_secret=lkjh' \
|
||||
|
@ -238,8 +240,7 @@ class ExternalToolsController < ApplicationController
|
|||
#
|
||||
# This would create a tool on the account with navigation for the user profile page
|
||||
# curl 'http://<canvas>/api/v1/accounts/<account_id>/external_tools' \
|
||||
# -u '<username>:<password>' \
|
||||
# -F 'api_key=<key>' \
|
||||
# -F 'access_token=<token>' \
|
||||
# -F 'name=LTI Example' \
|
||||
# -F 'consumer_key=asdfg' \
|
||||
# -F 'shared_secret=lkjh' \
|
||||
|
@ -247,6 +248,17 @@ class ExternalToolsController < ApplicationController
|
|||
# -F 'privacy_level=name_only' \
|
||||
# -F 'user_navigation[url]=http://example.com/ims/lti/user_endpoint' \
|
||||
# -F 'user_navigation[text]=Soemthing Cool'
|
||||
#
|
||||
# @example_request
|
||||
#
|
||||
# This would create a tool on the account with configuration pulled from an external URL
|
||||
# curl 'http://<canvas>/api/v1/accounts/<account_id>/external_tools' \
|
||||
# -F 'access_token=<token>' \
|
||||
# -F 'name=LTI Example' \
|
||||
# -F 'consumer_key=asdfg' \
|
||||
# -F 'shared_secret=lkjh' \
|
||||
# -F 'config_type=by_url' \
|
||||
# -F 'config_url=http://example.com/ims/lti/tool_config.xml'
|
||||
def create
|
||||
if authorized_action(@context, @current_user, :update)
|
||||
@tool = @context.context_external_tools.new
|
||||
|
@ -272,8 +284,7 @@ class ExternalToolsController < ApplicationController
|
|||
#
|
||||
# This would update the specified keys on this external tool
|
||||
# curl 'http://<canvas>/api/v1/courses/<course_id>/external_tools/<external_tool_id>' \
|
||||
# -u '<username>:<password>' \
|
||||
# -F 'api_key=<key>' \
|
||||
# -F 'access_token=<token>' \
|
||||
# -F 'name=Public Example' \
|
||||
# -F 'privacy_level=public'
|
||||
def update
|
||||
|
@ -318,7 +329,8 @@ class ExternalToolsController < ApplicationController
|
|||
def set_tool_attributes(tool, params)
|
||||
[:name, :description, :url, :domain, :privacy_level, :consumer_key, :shared_secret,
|
||||
:custom_fields, :custom_fields_string, :account_navigation, :user_navigation,
|
||||
:course_navigation, :editor_button, :resource_selection].each do |prop|
|
||||
:course_navigation, :editor_button, :resource_selection,
|
||||
:config_type, :config_url, :config_xml].each do |prop|
|
||||
tool.send("#{prop}=", params[prop]) if params.has_key?(prop)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -458,7 +458,7 @@ var I18n = I18n || {};
|
|||
contexts += @context.account_chain if @context.respond_to?(:account_chain)
|
||||
contexts << @domain_root_account if @domain_root_account
|
||||
Rails.cache.fetch((['editor_buttons_for'] + contexts.uniq).cache_key) do
|
||||
tools = ContextExternalTool.having_setting('editor_button').scoped(:conditions => contexts.map{|context| "(context_type='#{context.class.base_class.to_s}' AND context_id=#{context.id})"}.join(" OR "))
|
||||
tools = ContextExternalTool.active.having_setting('editor_button').scoped(:conditions => contexts.map{|context| "(context_type='#{context.class.base_class.to_s}' AND context_id=#{context.id})"}.join(" OR "))
|
||||
tools.sort_by(&:id).map do |tool|
|
||||
{
|
||||
:name => tool.label_for(:editor_button, nil),
|
||||
|
|
|
@ -7,14 +7,17 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
attr_accessible :privacy_level, :domain, :url, :shared_secret, :consumer_key,
|
||||
:name, :description, :custom_fields, :custom_fields_string,
|
||||
:course_navigation, :account_navigation, :user_navigation,
|
||||
:resource_selection, :editor_button
|
||||
:resource_selection, :editor_button,
|
||||
:config_type, :config_url, :config_xml
|
||||
validates_presence_of :name
|
||||
validates_presence_of :consumer_key
|
||||
validates_presence_of :shared_secret
|
||||
validate :url_or_domain_is_set
|
||||
serialize :settings
|
||||
attr_accessor :config_type, :config_url, :config_xml
|
||||
|
||||
before_save :infer_defaults
|
||||
validate :check_for_xml_error
|
||||
|
||||
workflow do
|
||||
state :anonymous
|
||||
|
@ -44,9 +47,24 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
|
||||
def label_for(key, lang=nil)
|
||||
labels = settings[key] && settings[key][:labels]
|
||||
(labels && labels[lang]) || (settings[key] && settings[key][:text]) || name || "External Tool"
|
||||
(labels && labels[lang]) ||
|
||||
(labels && lang && labels[lang.split('-').first]) ||
|
||||
(settings[key] && settings[key][:text]) ||
|
||||
settings[:text] || name || "External Tool"
|
||||
end
|
||||
|
||||
def xml_error(error)
|
||||
@xml_error = error
|
||||
end
|
||||
|
||||
def check_for_xml_error
|
||||
if @xml_error
|
||||
errors.add_to_base(@xml_error)
|
||||
false
|
||||
end
|
||||
end
|
||||
protected :check_for_xml_error
|
||||
|
||||
def readable_state
|
||||
workflow_state.titleize
|
||||
end
|
||||
|
@ -67,6 +85,43 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
}.join("\n")
|
||||
end
|
||||
|
||||
def config_type=(val)
|
||||
@config_type = val
|
||||
process_extended_configuration
|
||||
end
|
||||
|
||||
def config_xml=(val)
|
||||
@config_xml = val
|
||||
process_extended_configuration
|
||||
end
|
||||
|
||||
def config_url=(val)
|
||||
@config_url = val
|
||||
process_extended_configuration
|
||||
end
|
||||
|
||||
def process_extended_configuration
|
||||
return unless (config_type == 'by_url' && config_url) || (config_type == 'by_xml' && config_xml)
|
||||
tool_hash = nil
|
||||
begin
|
||||
converter = CC::Importer::Canvas::Converter.new({:no_archive_file => true})
|
||||
if config_type == 'by_url'
|
||||
tool_hash = converter.retrieve_and_convert_blti_url(config_url)
|
||||
else
|
||||
tool_hash = converter.convert_blti_xml(config_xml)
|
||||
end
|
||||
rescue CC::Importer::BLTIConverter::CCImportError => e
|
||||
tool_hash = {:error => e.message}
|
||||
end
|
||||
real_name = self.name
|
||||
if tool_hash[:error]
|
||||
xml_error(tool_hash[:error])
|
||||
else
|
||||
ContextExternalTool.import_from_migration(tool_hash, self.context, self)
|
||||
end
|
||||
self.name = real_name unless real_name.blank?
|
||||
end
|
||||
|
||||
def custom_fields_string=(str)
|
||||
hash = {}
|
||||
str.split(/\n/).each do |line|
|
||||
|
@ -354,8 +409,8 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
item.url = hash[:url] unless hash[:url].blank?
|
||||
item.domain = hash[:domain] unless hash[:domain].blank?
|
||||
item.privacy_level = hash[:privacy_level] || 'name_only'
|
||||
item.consumer_key = 'fake'
|
||||
item.shared_secret = 'fake'
|
||||
item.consumer_key ||= 'fake'
|
||||
item.shared_secret ||= 'fake'
|
||||
item.settings = hash[:settings].with_indifferent_access if hash[:settings].is_a?(Hash)
|
||||
if hash[:custom_fields].is_a? Hash
|
||||
item.settings[:custom_fields] ||= {}
|
||||
|
|
|
@ -13,7 +13,33 @@
|
|||
+opacity(0.5)
|
||||
.content
|
||||
padding: 0 20px 5px
|
||||
.extras
|
||||
display: none
|
||||
div
|
||||
font-style: italic
|
||||
font-size: 0.9em
|
||||
&:hover
|
||||
background-color: #eee
|
||||
.links
|
||||
+opacity(1.0)
|
||||
+opacity(1.0)
|
||||
&.has_editor_button,&.has_resource_selection,&.has_course_navigation,&.has_user_navigation,&.has_account_navigation
|
||||
.extras
|
||||
display: table-row
|
||||
div
|
||||
display: none
|
||||
&.has_editor_button
|
||||
div.editor_button
|
||||
display: block
|
||||
&.has_resource_selection
|
||||
div.resource_selection
|
||||
display: block
|
||||
&.has_course_navigation
|
||||
div.course_nagivation
|
||||
display: block
|
||||
&.has_user_navigation
|
||||
div.user_navigation
|
||||
display: block
|
||||
&.has_account_navigation
|
||||
div.account_navigation
|
||||
display: block
|
||||
|
|
@ -1,5 +1,16 @@
|
|||
<% tool ||= external_tool %>
|
||||
<div class="external_tool <%= 'blank' unless tool %>" id="external_tool_<%= tool ? tool.id : 'blank' %>" data-id="<%= tool && tool.id %>" style="<%= hidden unless tool %>" data-workflow_state="<%= tool.try(:workflow_state) %>">
|
||||
<%
|
||||
tool ||= external_tool
|
||||
classes = []
|
||||
if tool
|
||||
classes << "has_editor_button" if tool.try(:has_editor_button)
|
||||
classes << "has_resource_selection" if tool.try(:has_resource_selection)
|
||||
classes << "has_course_navigation" if tool.try(:has_course_navigation)
|
||||
classes << "has_account_navigation" if tool.try(:has_account_navigation)
|
||||
classes << "has_user_navigation" if tool.try(:has_user_navigation)
|
||||
end
|
||||
%>
|
||||
|
||||
<div class="external_tool <%= classes.join(" ") %> <%= 'blank' unless tool %>" id="external_tool_<%= tool ? tool.id : 'blank' %>" data-id="<%= tool && tool.id %>" style="<%= hidden unless tool %>" data-workflow_state="<%= tool.try(:workflow_state) %>">
|
||||
<div class="header">
|
||||
<div class="name"><%= tool.try(:name) %></div>
|
||||
<div class="links">
|
||||
|
@ -28,6 +39,15 @@
|
|||
<td class="description" style="font-size: 0.8em;">
|
||||
<%= tool.try(:description) %>
|
||||
</td>
|
||||
</tr><tr class="extras">
|
||||
<td><%= before_label :extras, "Extras" %></td>
|
||||
<td>
|
||||
<div class="editor_button"><%= t :editor_button_configured, "Editor button configured" %></div>
|
||||
<div class="resource_selection"><%= t :resource_selection_configured, "Resource selection configured" %></div>
|
||||
<div class="course_navigation"><%= t :course_navigation_configured, "Course navigation configured" %></div>
|
||||
<div class="user_navigation"><%= t :user_navigation_configured, "User navigation configured" %></div>
|
||||
<div class="account_navigation"><%= t :account_navigation_configured, "Account navigation configured" %></div>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
|
|
|
@ -9,55 +9,74 @@
|
|||
<a href="<%= context_url(@context, :context_external_tools_url) %>" class="external_tools_url" style="display: none;"> </a>
|
||||
<% form_for :external_tool, :url => '.', :html => {:id => 'external_tool_form'} do |f| %>
|
||||
<table class="formtable">
|
||||
<tr>
|
||||
<td><%= f.blabel :name, :en => "Name" %></td>
|
||||
<td><%= f.text_field :name %></td>
|
||||
</tr><tr>
|
||||
<td><%= f.blabel :consumer_key, :en => "Consumer Key" %></td>
|
||||
<td><%= f.text_field :consumer_key %></td>
|
||||
</tr><tr>
|
||||
<td style="vertical-align: top;"><%= f.blabel :shared_secret, :en => "Shared Secret" %></td>
|
||||
<td>
|
||||
<%= f.text_field :shared_secret %>
|
||||
<div style="font-size: 0.8em;" class="shared_secret_note"><%= t :shared_secret_note, "enter a new value to change" %></div>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td><label for="external_tool_match_by"><%= before_label :match_by, "Match By" %></label></td>
|
||||
<td>
|
||||
<select id="external_tool_match_by">
|
||||
<option value="domain"><%= t :domain, "Domain" %></option>
|
||||
<option value="url"><%= t :url, "URL" %></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr><tr class='tool_url'>
|
||||
<td><%= f.blabel :url, :en => "URL" %></td>
|
||||
<td><%= f.text_field :url %></td>
|
||||
</tr><tr class='tool_domain'>
|
||||
<td><%= f.blabel :domain, :en => "Domain" %></td>
|
||||
<td><%= f.text_field :domain %></td>
|
||||
</tr><tr>
|
||||
<td><%= f.blabel :privacy_level, :en => "Privacy" %></td>
|
||||
<td><%= f.select :privacy_level, [[t(:anonymous, "Anonymous"),'anonymous'],[t(:name_only, "Name Only"),'name_only'],[t(:public, "Public"),'public']] %></td>
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<%= f.blabel :custom_fields_string, :en => "Custom Fields" %>
|
||||
<span style="font-size: 0.8em; color: #888;"><%= t('custom_fields_explanation', '(one per line, format: name=value)') %></span>
|
||||
<br/>
|
||||
<%= f.text_area :custom_fields_string, :style => "width: 550px; height: 30px;" %>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<%= f.blabel :description, :en => "Description" %><br/>
|
||||
<%= f.text_area :description, :style => "width: 550px; height: 75px;" %>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<div class="button-container">
|
||||
<button class="button save_button" type="submit"><%= t "#buttons.save_tool_settings", "Save Tool Settings" %></button>
|
||||
<button class="button button-secondary cancel_button" type="button"><%= t "#buttons.cancel", "Cancel" %></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><%= f.blabel :name, :en => "Name" %></td>
|
||||
<td><%= f.text_field :name %></td>
|
||||
</tr><tr>
|
||||
<td><%= f.blabel :consumer_key, :en => "Consumer Key" %></td>
|
||||
<td><%= f.text_field :consumer_key %></td>
|
||||
</tr><tr>
|
||||
<td style="vertical-align: top;"><%= f.blabel :shared_secret, :en => "Shared Secret" %></td>
|
||||
<td>
|
||||
<%= f.text_field :shared_secret %>
|
||||
<div style="font-size: 0.8em;" class="shared_secret_note"><%= t :shared_secret_note, "enter a new value to change" %></div>
|
||||
</td>
|
||||
</tr><tr class="config_type_option">
|
||||
<td><%= f.blabel :config_type, :en => "Configuration Type" %></td>
|
||||
<td><%= f.select :config_type, [[t(:manual, "Manual Entry"),'manual'],[t(:by_url, "By URL"),'by_url'],[t(:by_xml, "Paste XML"),'by_xml']] %></td>
|
||||
</tr>
|
||||
</tbody><tbody class="config_type by_url">
|
||||
<tr>
|
||||
<td><%= f.blabel :config_url, :en => "Configuration URL" %></td>
|
||||
<td><%= f.text_field :config_url %></td>
|
||||
</tr>
|
||||
</tbody><tbody class="config_type by_xml">
|
||||
<tr>
|
||||
<td><%= f.blabel :config_xml, :en => "Paste XML Here" %></td>
|
||||
<td><%= f.text_area :config_xml, :style => "width: 300px; height: 60px;" %></td>
|
||||
</tr>
|
||||
</tbody><tbody class="config_type manual">
|
||||
<tr>
|
||||
<td><label for="external_tool_match_by"><%= before_label :match_by, "Match By" %></label></td>
|
||||
<td>
|
||||
<select id="external_tool_match_by">
|
||||
<option value="domain"><%= t :domain, "Domain" %></option>
|
||||
<option value="url"><%= t :url, "URL" %></option>
|
||||
</select>
|
||||
</td>
|
||||
</tr><tr class='tool_url'>
|
||||
<td><%= f.blabel :url, :en => "URL" %></td>
|
||||
<td><%= f.text_field :url %></td>
|
||||
</tr><tr class='tool_domain'>
|
||||
<td><%= f.blabel :domain, :en => "Domain" %></td>
|
||||
<td><%= f.text_field :domain %></td>
|
||||
</tr><tr>
|
||||
<td><%= f.blabel :privacy_level, :en => "Privacy" %></td>
|
||||
<td><%= f.select :privacy_level, [[t(:anonymous, "Anonymous"),'anonymous'],[t(:name_only, "Name Only"),'name_only'],[t(:public, "Public"),'public']] %></td>
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<%= f.blabel :custom_fields_string, :en => "Custom Fields" %>
|
||||
<span style="font-size: 0.8em; color: #888;"><%= t('custom_fields_explanation', '(one per line, format: name=value)') %></span>
|
||||
<br/>
|
||||
<%= f.text_area :custom_fields_string, :style => "width: 550px; height: 30px;" %>
|
||||
</td>
|
||||
</tr><tr>
|
||||
<td colspan="2">
|
||||
<%= f.blabel :description, :en => "Description" %><br/>
|
||||
<%= f.text_area :description, :style => "width: 550px; height: 75px;" %>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody><tbody>
|
||||
<tr>
|
||||
<td colspan="2">
|
||||
<div class="button-container">
|
||||
<button class="button save_button" type="submit"><%= t "#buttons.save_tool_settings", "Save Tool Settings" %></button>
|
||||
<button class="button button-secondary cancel_button" type="button"><%= t "#buttons.cancel", "Cancel" %></button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<% end %>
|
||||
</div>
|
||||
|
|
|
@ -34,7 +34,7 @@ class Migrator
|
|||
@course = {:file_map=>{}, :wikis=>[]}
|
||||
@course[:name] = @settings[:course_name]
|
||||
|
||||
return if settings[:testing]
|
||||
return if settings[:no_archive_file]
|
||||
|
||||
unless settings[:archive_file]
|
||||
MigratorHelper::download_archive(settings)
|
||||
|
|
|
@ -86,6 +86,24 @@ module CC::Importer
|
|||
tool
|
||||
end
|
||||
|
||||
def convert_blti_xml(xml)
|
||||
doc = Nokogiri::XML(xml)
|
||||
begin
|
||||
convert_blti_link(doc)
|
||||
rescue Nokogiri::XML::XPath::SyntaxError
|
||||
raise CCImportError.new(I18n.t(:invalid_xml_syntax, "invalid xml syntax"))
|
||||
end
|
||||
end
|
||||
|
||||
def retrieve_and_convert_blti_url(url)
|
||||
begin
|
||||
config_xml = Net::HTTP.get(URI.parse(url))
|
||||
convert_blti_xml(config_xml)
|
||||
rescue Timeout::Error
|
||||
raise CCImportError.new(I18n.t(:retrieve_timeout, "could not retrieve configuration, the server response timed out"))
|
||||
end
|
||||
end
|
||||
|
||||
def get_custom_properties(node)
|
||||
props = {}
|
||||
node.children.each do |property|
|
||||
|
@ -107,6 +125,6 @@ module CC::Importer
|
|||
end
|
||||
"blti"
|
||||
end
|
||||
|
||||
class CCImportError < Exception; end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -6,6 +6,8 @@ $(document).ready(function() {
|
|||
var formData = {
|
||||
domain: "",
|
||||
url: "",
|
||||
config_url: "",
|
||||
config_xml: "",
|
||||
description: "",
|
||||
name: "",
|
||||
custom_fields_string: "",
|
||||
|
@ -24,7 +26,9 @@ $(document).ready(function() {
|
|||
.attr('method', 'POST')
|
||||
.attr('action', $dialog.find(".external_tools_url").attr('href'));
|
||||
$dialog.fillFormData(formData, {object_name: 'external_tool'});
|
||||
$dialog.find(".config_type_option").show();
|
||||
$("#external_tool_match_by").val('domain').change();
|
||||
$("#external_tool_config_type").val('manual').change();
|
||||
});
|
||||
$dialog.find("form").formSubmit({
|
||||
beforeSubmit: function(data) {
|
||||
|
@ -44,6 +48,12 @@ $(document).ready(function() {
|
|||
hrefValues: ['id'],
|
||||
id: 'external_tool_' + tool.id
|
||||
});
|
||||
$tool
|
||||
.toggleClass('has_editor_button', tool.has_editor_button)
|
||||
.toggleClass('has_resource_selection', tool.has_resource_selection)
|
||||
.toggleClass('has_course_navigation', tool.has_course_navigation)
|
||||
.toggleClass('has_user_navigation', tool.has_user_navigation)
|
||||
.toggleClass('has_account_navigation', tool.has_account_navigation);
|
||||
$tool.find(".tool_url").showIf(tool.url).end()
|
||||
.find(".tool_domain").showIf(tool.domain);
|
||||
$tool.show();
|
||||
|
@ -73,6 +83,8 @@ $(document).ready(function() {
|
|||
width: 600,
|
||||
height: 420
|
||||
}).dialog('open');
|
||||
$dialog.find(".config_type_option").hide();
|
||||
$("#external_tool_config_type").val('manual').change();
|
||||
}).delegate('.delete_tool_link', 'click', function(event) {
|
||||
event.preventDefault();
|
||||
var $tool = $(this).parents(".external_tool");
|
||||
|
@ -96,5 +108,9 @@ $(document).ready(function() {
|
|||
.find(".tool_domain").show();
|
||||
}
|
||||
});
|
||||
$("#external_tool_config_type").change(function(event) {
|
||||
$("#external_tool_form .config_type").hide();
|
||||
$("#external_tool_form .config_type." + $(this).val()).show();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -89,7 +89,7 @@
|
|||
$list.empty();
|
||||
$.each(options.options, function(optionName, callback){
|
||||
var $option = $("<div class='option minimal' style='cursor: pointer; padding: 2px 5px; overflow: hidden; white-space: nowrap;'>" +
|
||||
" <span tabindex='-1'>" + optionName.replace(/_/g, " ") + "</span>" +
|
||||
" <span tabindex='-1'>" + optionName + "</span>" +
|
||||
"</div>").appendTo($list);
|
||||
|
||||
if($.isFunction(callback)) {
|
||||
|
|
|
@ -93,4 +93,138 @@ describe ExternalToolsController do
|
|||
assigns[:tool].should == tool
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST 'create'" do
|
||||
it "should require authentication" do
|
||||
course_with_teacher(:active_all => true)
|
||||
post 'create', :course_id => @course.id
|
||||
response.should be_redirect
|
||||
end
|
||||
|
||||
it "should accept basic configurations" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
post 'create', :course_id => @course.id, :external_tool => {:name => "tool name", :url => "http://example.com", :consumer_key => "key", :shared_secret => "secret"}
|
||||
response.should be_success
|
||||
assigns[:tool].should_not be_nil
|
||||
assigns[:tool].name.should == "tool name"
|
||||
assigns[:tool].url.should == "http://example.com"
|
||||
assigns[:tool].consumer_key.should == "key"
|
||||
assigns[:tool].shared_secret.should == "secret"
|
||||
end
|
||||
|
||||
it "should handle advanced xml configurations" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
xml = <<-XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
|
||||
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
|
||||
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
|
||||
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
|
||||
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
|
||||
<blti:title>Other Name</blti:title>
|
||||
<blti:description>Description</blti:description>
|
||||
<blti:launch_url>http://example.com/other_url</blti:launch_url>
|
||||
<blti:extensions platform="canvas.instructure.com">
|
||||
<lticm:property name="privacy_level">public</lticm:property>
|
||||
<lticm:options name="editor_button">
|
||||
<lticm:property name="url">http://example.com/editor</lticm:property>
|
||||
<lticm:property name="icon_url">http://example.com/icon.png</lticm:property>
|
||||
<lticm:property name="text">Editor Button</lticm:property>
|
||||
<lticm:property name="selection_width">500</lticm:property>
|
||||
<lticm:property name="selection_height">300</lticm:property>
|
||||
</lticm:options>
|
||||
</blti:extensions>
|
||||
<cartridge_bundle identifierref="BLTI001_Bundle"/>
|
||||
<cartridge_icon identifierref="BLTI001_Icon"/>
|
||||
</cartridge_basiclti_link>
|
||||
XML
|
||||
post 'create', :course_id => @course.id, :external_tool => {:name => "tool name", :url => "http://example.com", :consumer_key => "key", :shared_secret => "secret", :config_type => "by_xml", :config_xml => xml}
|
||||
response.should be_success
|
||||
assigns[:tool].should_not be_nil
|
||||
# User-entered name overrides name provided in xml
|
||||
assigns[:tool].name.should == "tool name"
|
||||
assigns[:tool].description.should == "Description"
|
||||
assigns[:tool].url.should == "http://example.com/other_url"
|
||||
assigns[:tool].consumer_key.should == "key"
|
||||
assigns[:tool].shared_secret.should == "secret"
|
||||
assigns[:tool].has_editor_button.should be_true
|
||||
end
|
||||
|
||||
it "should fail gracefully on invalid xml configurations" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
xml = "bob"
|
||||
post 'create', :course_id => @course.id, :external_tool => {:name => "tool name", :url => "http://example.com", :consumer_key => "key", :shared_secret => "secret", :config_type => "by_xml", :config_xml => xml}
|
||||
response.should_not be_success
|
||||
assigns[:tool].should be_new_record
|
||||
json = json_parse(response.body)
|
||||
json['errors']['base'][0]['message'].should == I18n.t(:invalid_xml_syntax, 'invalid xml syntax')
|
||||
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
xml = "<a><b>c</b></a>"
|
||||
post 'create', :course_id => @course.id, :external_tool => {:name => "tool name", :url => "http://example.com", :consumer_key => "key", :shared_secret => "secret", :config_type => "by_xml", :config_xml => xml}
|
||||
response.should_not be_success
|
||||
assigns[:tool].should be_new_record
|
||||
json = json_parse(response.body)
|
||||
json['errors']['base'][0]['message'].should == I18n.t(:invalid_xml_syntax, 'invalid xml syntax')
|
||||
end
|
||||
|
||||
it "should handle advanced xml configurations by URL retrieval" do
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
xml = <<-XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
|
||||
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
|
||||
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
|
||||
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
|
||||
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
|
||||
<blti:title>Other Name</blti:title>
|
||||
<blti:description>Description</blti:description>
|
||||
<blti:launch_url>http://example.com/other_url</blti:launch_url>
|
||||
<blti:extensions platform="canvas.instructure.com">
|
||||
<lticm:property name="privacy_level">public</lticm:property>
|
||||
<lticm:options name="editor_button">
|
||||
<lticm:property name="url">http://example.com/editor</lticm:property>
|
||||
<lticm:property name="icon_url">http://example.com/icon.png</lticm:property>
|
||||
<lticm:property name="text">Editor Button</lticm:property>
|
||||
<lticm:property name="selection_width">500</lticm:property>
|
||||
<lticm:property name="selection_height">300</lticm:property>
|
||||
</lticm:options>
|
||||
</blti:extensions>
|
||||
<cartridge_bundle identifierref="BLTI001_Bundle"/>
|
||||
<cartridge_icon identifierref="BLTI001_Icon"/>
|
||||
</cartridge_basiclti_link>
|
||||
XML
|
||||
Net::HTTP.expects(:get).with(URI.parse("http://config.example.com")).returns(xml)
|
||||
post 'create', :course_id => @course.id, :external_tool => {:name => "tool name", :url => "http://example.com", :consumer_key => "key", :shared_secret => "secret", :config_type => "by_url", :config_url => "http://config.example.com"}
|
||||
response.should be_success
|
||||
assigns[:tool].should_not be_nil
|
||||
# User-entered name overrides name provided in xml
|
||||
assigns[:tool].name.should == "tool name"
|
||||
assigns[:tool].description.should == "Description"
|
||||
assigns[:tool].url.should == "http://example.com/other_url"
|
||||
assigns[:tool].consumer_key.should == "key"
|
||||
assigns[:tool].shared_secret.should == "secret"
|
||||
assigns[:tool].has_editor_button.should be_true
|
||||
end
|
||||
|
||||
it "should fail gracefully on invalid URL retrieval or timeouts" do
|
||||
Net::HTTP.expects(:get).with(URI.parse("http://config.example.com")).raises(Timeout::Error)
|
||||
course_with_teacher_logged_in(:active_all => true)
|
||||
xml = "bob"
|
||||
post 'create', :course_id => @course.id, :external_tool => {:name => "tool name", :url => "http://example.com", :consumer_key => "key", :shared_secret => "secret", :config_type => "by_url", :config_url => "http://config.example.com"}
|
||||
response.should_not be_success
|
||||
assigns[:tool].should be_new_record
|
||||
json = json_parse(response.body)
|
||||
json['errors']['base'][0]['message'].should == I18n.t(:retrieve_timeout, 'could not retrieve configuration, the server response timed out')
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
|
|
|
@ -21,11 +21,11 @@ require File.expand_path(File.dirname(__FILE__) + '/../../spec_helper.rb')
|
|||
CC_XML_EXPORT_DIR = File.dirname(__FILE__) + '/../../fixtures/cc/cc_export'
|
||||
|
||||
def get_cc_converter
|
||||
CC::Importer::Canvas::Converter.new({:testing=>true})
|
||||
CC::Importer::Canvas::Converter.new({:no_archive_file=>true})
|
||||
end
|
||||
|
||||
def get_standard_converter
|
||||
CC::Importer::Standard::Converter.new({:testing=>true})
|
||||
CC::Importer::Standard::Converter.new({:no_archive_file=>true})
|
||||
end
|
||||
|
||||
def get_cc_export_file(rel_path)
|
||||
|
|
|
@ -132,7 +132,7 @@ describe "Canvas Cartridge importing" do
|
|||
a_2 = ag2_2.assignments.first
|
||||
ag2_2.rules.should == "drop_lowest:2\ndrop_highest:5\nnever_drop:%s\n" % a_2.id
|
||||
end
|
||||
|
||||
|
||||
it "should import external tools" do
|
||||
tool1 = @copy_from.context_external_tools.new
|
||||
tool1.url = 'http://instructure.com'
|
||||
|
|
|
@ -252,6 +252,57 @@ describe ContextExternalTool do
|
|||
end
|
||||
end
|
||||
|
||||
describe "label_for" do
|
||||
it "should return the tool name if nothing else is configured and no key is sent" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.save!
|
||||
tool.label_for(nil).should == 'tool'
|
||||
end
|
||||
|
||||
it "should return the tool name if nothing is configured on the sent key" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.settings = {:course_navigation => {:bob => 'asfd'}}
|
||||
tool.save!
|
||||
tool.label_for(:course_navigation).should == 'tool'
|
||||
end
|
||||
|
||||
it "should return the tool's 'text' value if no key is sent" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.settings = {:text => 'tool label', :course_navigation => {:url => "http://example.com", :text => 'course nav'}}
|
||||
tool.save!
|
||||
tool.label_for(nil).should == 'tool label'
|
||||
end
|
||||
|
||||
it "should return the tool's 'text' value if no 'text' value is set for the sent key" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.settings = {:text => 'tool label', :course_navigation => {:bob => 'asdf'}}
|
||||
tool.save!
|
||||
tool.label_for(:course_navigation).should == 'tool label'
|
||||
end
|
||||
|
||||
it "should return the setting's 'text' value for the sent key if available" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.settings = {:text => 'tool label', :course_navigation => {:url => "http://example.com", :text => 'course nav'}}
|
||||
tool.save!
|
||||
tool.label_for(:course_navigation).should == 'course nav'
|
||||
end
|
||||
|
||||
it "should return the locale-specific label if specified and matching exactly" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.settings = {:text => 'tool label', :course_navigation => {:url => "http://example.com", :text => 'course nav', :labels => {'en-US' => 'english nav'}}}
|
||||
tool.save!
|
||||
tool.label_for(:course_navigation, 'en-US').should == 'english nav'
|
||||
tool.label_for(:course_navigation, 'es').should == 'course nav'
|
||||
end
|
||||
|
||||
it "should return the locale-specific label if specified and matching based on general locale" do
|
||||
tool = @root_account.context_external_tools.new(:name => 'tool', :consumer_key => '12345', :shared_secret => 'secret', :url => "http://example.com")
|
||||
tool.settings = {:text => 'tool label', :course_navigation => {:url => "http://example.com", :text => 'course nav', :labels => {'en' => 'english nav'}}}
|
||||
tool.save!
|
||||
tool.label_for(:course_navigation, 'en-US').should == 'english nav'
|
||||
end
|
||||
end
|
||||
|
||||
describe "find_for" do
|
||||
def new_external_tool(context)
|
||||
context.context_external_tools.new(:name => "bob", :consumer_key => "bob", :shared_secret => "bob", :domain => "google.com")
|
||||
|
|
|
@ -14,6 +14,14 @@ describe "editing external tools" do
|
|||
driver.find_element(:css, "#external_tool_name").send_keys "Tool"
|
||||
driver.find_element(:css, "#external_tool_consumer_key").send_keys "Key"
|
||||
driver.find_element(:css, "#external_tool_shared_secret").send_keys "Secret"
|
||||
|
||||
driver.find_element(:css, "#external_tool_match_by option[value='url']").click
|
||||
driver.find_element(:css, "#external_tool_domain").should_not be_displayed
|
||||
driver.find_element(:css, "#external_tool_url").should be_displayed
|
||||
driver.find_element(:css, "#external_tool_match_by option[value='domain']").click
|
||||
driver.find_element(:css, "#external_tool_domain").should be_displayed
|
||||
driver.find_element(:css, "#external_tool_url").should_not be_displayed
|
||||
|
||||
driver.find_element(:css, "#external_tool_domain").send_keys "example.com"
|
||||
driver.find_element(:css, "#external_tool_custom_fields_string").send_keys "a=1\nb=123"
|
||||
driver.find_element(:css, "#external_tools_dialog .save_button").click
|
||||
|
@ -21,15 +29,108 @@ describe "editing external tools" do
|
|||
keep_trying_until { !driver.find_element(:css, "#external_tools_dialog").displayed? }
|
||||
|
||||
tool = ContextExternalTool.last
|
||||
driver.find_element(:css, "#external_tool_#{tool.id}").should be_displayed
|
||||
tool_elem = driver.find_element(:css, "#external_tool_#{tool.id}")
|
||||
tool_elem.should be_displayed
|
||||
tool.should_not be_nil
|
||||
tool.name.should == "Tool"
|
||||
tool.consumer_key.should == "Key"
|
||||
tool.shared_secret.should == "Secret"
|
||||
tool.domain.should == "example.com"
|
||||
tool_elem.attribute('class').should_not match(/has_editor_button|has_resource_selection|has_course_navigation|has_account_navigation|has_user_navigation/)
|
||||
tool.settings[:custom_fields].should == {'a' => '1', 'b' => '123'}
|
||||
end
|
||||
|
||||
it "should allow creating a new course external tool with extensions" do
|
||||
course_with_teacher_logged_in
|
||||
get "/courses/#{@course.id}/settings"
|
||||
|
||||
keep_trying_until { driver.find_element(:css, "#tab-tools-link").displayed? }
|
||||
driver.find_element(:css, "#tab-tools-link").click
|
||||
driver.find_element(:css, ".add_tool_link").click
|
||||
driver.find_element(:css, "#external_tools_dialog").should be_displayed
|
||||
driver.find_element(:css, "#external_tool_name").send_keys "Tool"
|
||||
driver.find_element(:css, "#external_tool_consumer_key").send_keys "Key"
|
||||
driver.find_element(:css, "#external_tool_shared_secret").send_keys "Secret"
|
||||
|
||||
driver.find_element(:css, "#external_tool_config_type option[value='by_url']").click
|
||||
driver.find_element(:css, "#external_tool_form .config_type.manual").should_not be_displayed
|
||||
driver.find_element(:css, "#external_tool_config_url").should be_displayed
|
||||
driver.find_element(:css, "#external_tool_config_xml").should_not be_displayed
|
||||
|
||||
driver.find_element(:css, "#external_tool_config_type option[value='by_xml']").click
|
||||
driver.find_element(:css, "#external_tool_form .config_type.manual").should_not be_displayed
|
||||
driver.find_element(:css, "#external_tool_config_url").should_not be_displayed
|
||||
driver.find_element(:css, "#external_tool_config_xml").should be_displayed
|
||||
xml = <<-XML
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<cartridge_basiclti_link xmlns="http://www.imsglobal.org/xsd/imslticc_v1p0"
|
||||
xmlns:blti = "http://www.imsglobal.org/xsd/imsbasiclti_v1p0"
|
||||
xmlns:lticm ="http://www.imsglobal.org/xsd/imslticm_v1p0"
|
||||
xmlns:lticp ="http://www.imsglobal.org/xsd/imslticp_v1p0"
|
||||
xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation = "http://www.imsglobal.org/xsd/imslticc_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticc_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imsbasiclti_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imsbasiclti_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imslticm_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticm_v1p0.xsd
|
||||
http://www.imsglobal.org/xsd/imslticp_v1p0 http://www.imsglobal.org/xsd/lti/ltiv1p0/imslticp_v1p0.xsd">
|
||||
<blti:title>Other Name</blti:title>
|
||||
<blti:description>Description</blti:description>
|
||||
<blti:launch_url>http://example.com/other_url</blti:launch_url>
|
||||
<blti:extensions platform="canvas.instructure.com">
|
||||
<lticm:property name="privacy_level">public</lticm:property>
|
||||
<lticm:options name="editor_button">
|
||||
<lticm:property name="url">http://example.com/editor</lticm:property>
|
||||
<lticm:property name="icon_url">http://example.com/icon.png</lticm:property>
|
||||
<lticm:property name="text">Editor Button</lticm:property>
|
||||
<lticm:property name="selection_width">500</lticm:property>
|
||||
<lticm:property name="selection_height">300</lticm:property>
|
||||
</lticm:options>
|
||||
<lticm:options name="resource_selection">
|
||||
<lticm:property name="url">https://example.com/wiki</lticm:property>
|
||||
<lticm:property name="text">Build/Link to Wiki Page</lticm:property>
|
||||
<lticm:property name="selection_width">500</lticm:property>
|
||||
<lticm:property name="selection_height">300</lticm:property>
|
||||
</lticm:options>
|
||||
<lticm:options name="course_navigation">
|
||||
<lticm:property name="url">https://example.com/attendance</lticm:property>
|
||||
<lticm:property name="text">Attendance</lticm:property>
|
||||
</lticm:options>
|
||||
<lticm:options name="user_navigation">
|
||||
<lticm:property name="url">https://example.com/attendance</lticm:property>
|
||||
<lticm:property name="text">Attendance</lticm:property>
|
||||
</lticm:options>
|
||||
<lticm:options name="account_navigation">
|
||||
<lticm:property name="url">https://example.com/attendance</lticm:property>
|
||||
<lticm:property name="text">Attendance</lticm:property>
|
||||
</lticm:options>
|
||||
</blti:extensions>
|
||||
<cartridge_bundle identifierref="BLTI001_Bundle"/>
|
||||
<cartridge_icon identifierref="BLTI001_Icon"/>
|
||||
</cartridge_basiclti_link>
|
||||
XML
|
||||
driver.find_element(:css, "#external_tool_config_xml").send_keys xml
|
||||
driver.find_element(:css, "#external_tools_dialog .save_button").click
|
||||
|
||||
keep_trying_until { !driver.find_element(:css, "#external_tools_dialog").displayed? }
|
||||
|
||||
tool = ContextExternalTool.last
|
||||
tool_elem = driver.find_element(:css, "#external_tool_#{tool.id}")
|
||||
tool_elem.should be_displayed
|
||||
tool.has_editor_button.should be_true
|
||||
tool.has_resource_selection.should be_true
|
||||
tool.has_course_navigation.should be_true
|
||||
tool.has_account_navigation.should be_true
|
||||
tool.has_user_navigation.should be_true
|
||||
tool_elem.attribute('class').should match(/has_editor_button/)
|
||||
tool_elem.attribute('class').should match(/has_resource_selection/)
|
||||
tool_elem.attribute('class').should match(/has_course_navigation/)
|
||||
tool_elem.attribute('class').should match(/has_account_navigation/)
|
||||
tool_elem.attribute('class').should match(/has_user_navigation/)
|
||||
tool.name.should == "Tool"
|
||||
tool.consumer_key.should == "Key"
|
||||
tool.shared_secret.should == "Secret"
|
||||
tool.url.should == "http://example.com/other_url"
|
||||
end
|
||||
|
||||
it "should allow editing an existing external tool with custom fields" do
|
||||
course_with_teacher_logged_in
|
||||
tool = @course.context_external_tools.create!(:name => "new tool", :consumer_key => "key", :shared_secret => "secret", :domain => 'example.com', :custom_fields => {'a' => '1', 'b' => '2'})
|
||||
|
|
Loading…
Reference in New Issue