add course_home_sub_navigation extension to external tools

this puts lti app links on the right side bar of the course
home page.

fixes RAN-53, RAN-55

test plan:
 - modify an lti app's xml to include the
   course_home_sub_navigation extension
 - configure an lti app via modified xml
 - navigate to a course home page, where you should see a button
   for the configured tool on the right sidebar.
 - clicking on the button should launch the tool.
 - configure another tool the same way, but include a
   "visibility" property in new extension with a value of
   "admins"
 - view the course home page as a teacher. you should be able
   to tee the newly configured tool, in addition to the
   previously configured tool.
 - view the course home page as a student. you should only see
   the first tool you configured, and not see the tool with the
   limited visibility property.

Change-Id: Ibe50c649f6d5f6806a87f0c8e8402f1209b9ed40
Reviewed-on: https://gerrit.instructure.com/35161
Reviewed-by: Brad Humphrey <brad@instructure.com>
Tested-by: Jenkins <jenkins@instructure.com>
QA-Review: Nathan Rogowski <nathan@instructure.com>
Product-Review: Jon Willesen <jonw@instructure.com>
This commit is contained in:
Jon Willesen 2014-05-19 12:34:11 -06:00
parent 08ae58b7a9
commit 3c65211658
9 changed files with 136 additions and 7 deletions

View File

@ -23,6 +23,7 @@ define [
{extension_type: 'user_navigation', text: I18n.t 'user_navigation_configured', 'User navigation configured'}
{extension_type: 'homework_submission', text: I18n.t 'homework_submission_configured', 'Homework submission configured'}
{extension_type: 'migration_selection', text: I18n.t 'migration_selection_configured', 'Migration selection configured'}
{extension_type: 'course_home_sub_navigation', text: I18n.t 'course_home_sub_navigation_configured', 'Course home sub navigation configured'}
]
json = super

View File

@ -1368,6 +1368,11 @@ class CoursesController < ApplicationController
if @current_user and (@show_recent_feedback = @context.user_is_student?(@current_user))
@recent_feedback = (@current_user && @current_user.recent_feedback(:contexts => @contexts)) || []
end
@course_home_sub_navigation_tools = ContextExternalTool.all_tools_for(@context).select(&:has_course_home_sub_navigation?)
unless @context.grants_right?(@current_user, session, :manage_content)
@course_home_sub_navigation_tools.reject! { |tool| tool.course_home_sub_navigation(:visibility) == 'admins' }
end
else
# clear notices that would have been displayed as a result of processing
# an enrollment invitation, since we're giving an error

View File

@ -9,6 +9,7 @@ class ContextExternalTool < ActiveRecord::Base
:name, :description, :custom_fields, :custom_fields_string,
:course_navigation, :account_navigation, :user_navigation,
:resource_selection, :editor_button, :homework_submission,
:course_home_sub_navigation,
:config_type, :config_url, :config_xml, :tool_id
validates_presence_of :context_id, :context_type, :workflow_state
@ -38,7 +39,7 @@ class ContextExternalTool < ActiveRecord::Base
can :read and can :update and can :delete
end
EXTENSION_TYPES = [:user_navigation, :course_navigation, :account_navigation, :resource_selection, :editor_button, :homework_submission, :migration_selection]
EXTENSION_TYPES = [:user_navigation, :course_navigation, :account_navigation, :resource_selection, :editor_button, :homework_submission, :migration_selection, :course_home_sub_navigation]
def url_or_domain_is_set
setting_types = EXTENSION_TYPES
# url or domain (or url on canvas lti extension) is required
@ -242,6 +243,18 @@ class ContextExternalTool < ActiveRecord::Base
extension_setting(:migration_selection, setting)
end
def course_home_sub_navigation=(hash)
tool_setting(:course_home_sub_navigation, hash, :icon_url) do |tool_settings|
if %w(members admins).include?(hash[:visibility])
tool_settings[:visibility] = hash[:visibility]
end
end
end
def course_home_sub_navigation(setting = nil)
extension_setting(:course_home_sub_navigation, setting)
end
def icon_url=(i_url)
settings[:icon_url] = i_url
end

View File

@ -17,8 +17,15 @@
<% @can_manage_content = can_do(@context, @current_user, :manage_content) %>
<% if @context.feature_enabled?(:draft_state) %>
<% if @can_manage_content || @course_home_view != 'feed' %>
<% if @can_manage_content || @course_home_view != 'feed' || @course_home_sub_navigation_tools.present? %>
<div class="course-options">
<% @course_home_sub_navigation_tools.each do |tool| %>
<a class="button-sidebar-wide course-home-sub-navigation-lti"
href="<%= course_external_tool_path(@context, tool, launch_type: 'course_home_sub_navigation') %>">
<img class="icon" src="<%= tool.course_home_sub_navigation(:icon_url) %>" />
<%= tool.label_for(:course_home_sub_navigation) %>
</a>
<% end %>
<% if @can_manage_content %>
<a class="button-sidebar-wide element_toggler" aria-controls="edit_course_home_content_form" href="<%= context_url(@context, :context_details_url) %>">
<i class="icon-target"></i>
@ -45,10 +52,19 @@
<% end %>
<% else %>
<% if @can_manage_content %>
<% if @can_manage_content || @course_home_sub_navigation_tools.present? %>
<div class="secondary-button-group rs-margin-lr rs-margin-top rs-margin-bottom">
<a href="#" class="btn button-sidebar-wide wizard_popup_link <%= 'auto_open' if @context.created? || @context.claimed? %>"><i class="icon-question"></i> <%= t('links.course_setup', %{Course Setup Checklist}) %></a>
<a class="btn button-sidebar-wide" href="<%= context_url(@context, :new_context_discussion_topic_url, :is_announcement => true) %>"><i class="icon-announcement"></i> <%= t('links.new_announcement', %{New Announcement}) %></a>
<% @course_home_sub_navigation_tools.each do |tool| %>
<a class="btn button-sidebar-wide course-home-sub-navigation-lti"
href="<%= course_external_tool_path(@context, tool, launch_type: 'course_home_sub_navigation') %>">
<img class="icon" src="<%= tool.course_home_sub_navigation(:icon_url) %>" />
<%= tool.label_for(:course_home_sub_navigation) %>
</a>
<% end %>
<% if @can_manage_content %>
<a href="#" class="btn button-sidebar-wide wizard_popup_link <%= 'auto_open' if @context.created? || @context.claimed? %>"><i class="icon-question"></i> <%= t('links.course_setup', %{Course Setup Checklist}) %></a>
<a class="btn button-sidebar-wide" href="<%= context_url(@context, :new_context_discussion_topic_url, :is_announcement => true) %>"><i class="icon-announcement"></i> <%= t('links.new_announcement', %{New Announcement}) %></a>
<% end %>
</div>
<% end %>
<% end %>

View File

@ -0,0 +1,12 @@
class AddCourseHomeNavigationForExternalTools < ActiveRecord::Migration
tag :predeploy
def self.up
add_column :context_external_tools, :has_course_home_sub_navigation, :boolean
add_index :context_external_tools, [:context_id, :context_type, :has_course_home_sub_navigation], :name => "external_tools_course_home_sub_navigation"
end
def self.down
remove_column :context_external_tools, :has_course_home_sub_navigation
remove_index :context_external_tools, :name => "external_tools_course_home_sub_navigation"
end
end

View File

@ -267,7 +267,6 @@ describe ExternalToolsController, type: :request do
json = api_call(:get, "/api/v1/#{type}s/#{context.id}/external_tools/#{et.id}.json",
{:controller => 'external_tools', :action => 'show', :format => 'json',
:"#{type}_id" => context.id.to_s, :external_tool_id => et.id.to_s})
HashDiff.diff(json, example_json(et)).should == []
end
@ -394,6 +393,7 @@ describe ExternalToolsController, type: :request do
et.homework_submission = {:url=>"http://www.example.com/ims/lti/editor", :selection_width=>50, :selection_height=>50, :text=>"homework submission"}
et.resource_selection = {:url=>"http://www.example.com/ims/lti/resource", :text => "", :selection_width=>50, :selection_height=>50}
et.migration_selection = {:url=>"http://www.example.com/ims/lti/resource", :text => "migration selection", :selection_width=>42, :selection_height=>24}
et.course_home_sub_navigation = {:url=>"http://www.example.com/ims/lti/resource", :text => "course home sub navigation", display_type: 'full_width', visibility: 'admins'}
et.save!
et
end
@ -502,6 +502,14 @@ describe ExternalToolsController, type: :request do
"label"=>"migration selection",
"url"=>"http://www.example.com/ims/lti/resource",
"selection_height"=>24,
"selection_width"=>42}}
"selection_width"=>42},
"course_home_sub_navigation"=>
{"text"=>"course home sub navigation",
"label"=>"course home sub navigation",
"url"=>"http://www.example.com/ims/lti/resource",
"visibility"=>'admins',
"display_type"=>'full_width',
"selection_height"=>400,
"selection_width"=>800}}
end
end

View File

@ -49,6 +49,7 @@ define [
},
"homework_submission": null,
"migration_selection": null,
"course_home_sub_navigation": null,
"icon_url": "https://www.edu-apps.org/tools/khan_academy/icon.png"
}
)

View File

@ -210,6 +210,74 @@ describe "courses" do
roles_to_sections[role_name].should == sections.first.text
end
end
context "course_home_sub_navigation lti apps" do
def create_course_home_sub_navigation_tool(options = {})
defaults = {
name: options[:name] || "external tool",
consumer_key: 'test',
shared_secret: 'asdf',
url: 'http://example.com/ims/lti',
course_home_sub_navigation: { icon_url: '/images/delete.png' },
}
@course.context_external_tools.create!(defaults.merge(options))
end
it "should display course_home_sub_navigation lti apps (draft state off)" do
course_with_teacher_logged_in(active_all: true)
num_tools = 3
num_tools.times { |index| create_course_home_sub_navigation_tool(name: "external tool #{index}") }
get "/courses/#{@course.id}"
ff(".course-home-sub-navigation-lti").size.should == num_tools
end
it "should display course_home_sub_navigation lti apps (draft state on)" do
course_with_teacher_logged_in(active_all: true)
@course.account.enable_feature!(:draft_state)
num_tools = 2
num_tools.times { |index| create_course_home_sub_navigation_tool(name: "external tool #{index}") }
get "/courses/#{@course.id}"
ff(".course-home-sub-navigation-lti").size.should == num_tools
end
it "should include launch type parameter (draft state off)" do
course_with_teacher_logged_in(active_all: true)
create_course_home_sub_navigation_tool
get "/courses/#{@course.id}"
f('.course-home-sub-navigation-lti').attribute("href").should match(/launch_type=course_home_sub_navigation/)
end
it "should include launch type parameter (draft state on)" do
course_with_teacher_logged_in(active_all: true)
@course.account.enable_feature!(:draft_state)
create_course_home_sub_navigation_tool
get "/courses/#{@course.id}"
f('.course-home-sub-navigation-lti').attribute("href").should match(/launch_type=course_home_sub_navigation/)
end
it "should only display active tools" do
course_with_teacher_logged_in(active_all: true)
tool = create_course_home_sub_navigation_tool
tool.workflow_state = 'deleted'
tool.save!
get "/courses/#{@course.id}"
ff(".course-home-sub-navigation-lti").size.should == 0
end
it "should not display admin tools to students" do
course_with_teacher_logged_in(active_all: true)
tool = create_course_home_sub_navigation_tool
tool.course_home_sub_navigation['visibility'] = 'admins'
tool.save!
get "/courses/#{@course.id}"
ff(".course-home-sub-navigation-lti").size.should == 1
course_with_student_logged_in(course: @course, active_all: true)
get "/courses/#{@course.id}"
ff(".course-home-sub-navigation-lti").size.should == 0
end
end
end
context "course as a student" do

View File

@ -160,6 +160,11 @@ shared_examples_for "external tools tests" do
<lticm:property name="selection_width">500</lticm:property>
<lticm:property name="selection_height">300</lticm:property>
</lticm:options>
<lticm:options name="course_home_sub_navigation">
<lticm:property name="url">https://example.com/wiki</lticm:property>
<lticm:property name="text">Build/Link to Wiki Page</lticm:property>
<lticm:property name="display_type">full_width</lticm:property>
</lticm:options>
XML
f("#external_tool_config_xml").send_keys <<-XML
<lticm:options name="user_navigation">