Skip to content

Commit

Permalink
xml config for blti extensions
Browse files Browse the repository at this point in the history
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 <[email protected]>
Reviewed-by: Brian Palmer <[email protected]>
  • Loading branch information
whitmer committed Dec 29, 2011
1 parent e08b0d2 commit b02a09c
Show file tree
Hide file tree
Showing 15 changed files with 523 additions and 71 deletions.
26 changes: 19 additions & 7 deletions app/controllers/external_tools_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -216,13 +216,15 @@ def render_tool(id, selection_type)
# @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' \
Expand All @@ -238,15 +240,25 @@ def render_tool(id, selection_type)
#
# 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' \
# -F 'url=http://example.com/ims/lti' \
# -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
Expand All @@ -272,8 +284,7 @@ def create
#
# 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
Expand Down Expand Up @@ -318,7 +329,8 @@ def destroy
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
Expand Down
2 changes: 1 addition & 1 deletion app/helpers/application_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -458,7 +458,7 @@ def editor_buttons
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),
Expand Down
63 changes: 59 additions & 4 deletions app/models/context_external_tool.rb
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -44,9 +47,24 @@ def settings

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
Expand All @@ -67,6 +85,43 @@ def custom_fields_string
}.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|
Expand Down Expand Up @@ -354,8 +409,8 @@ def self.import_from_migration(hash, context, item=nil)
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] ||= {}
Expand Down
28 changes: 27 additions & 1 deletion app/stylesheets/external_tools.sass
Original file line number Diff line number Diff line change
Expand Up @@ -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

24 changes: 22 additions & 2 deletions app/views/external_tools/_external_tool.html.erb
Original file line number Diff line number Diff line change
@@ -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">
Expand Down Expand Up @@ -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>
Expand Down
117 changes: 68 additions & 49 deletions app/views/external_tools/_external_tools.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -9,55 +9,74 @@
<a href="<%= context_url(@context, :context_external_tools_url) %>" class="external_tools_url" style="display: none;">&nbsp;</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>
Expand Down
2 changes: 1 addition & 1 deletion lib/canvas/migration/migrator.rb
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ def initialize(settings, migration_type)
@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)
Expand Down
Loading

0 comments on commit b02a09c

Please sign in to comment.