add module_index_menu LTI placement
test plan: * configure a tool with an module_index_menu placement (similar to the wiki_index_menu type) * enable the "Import Commons Favorites" feature * should launch the tool though a cog dropdown in the header of the modules page into a tray * closing the tray after a message has been posted should cause the modules page to refresh flag=commons_favorites closes #LA-71 #LA-72 Change-Id: I4ab15bf71da574482b107cbbba295cb4557f4fa8 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/217828 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Tested-by: Jenkins Reviewed-by: Carl Kibler <ckibler@instructure.com> QA-Review: Carl Kibler <ckibler@instructure.com> Product-Review: James Williams <jamesw@instructure.com>
This commit is contained in:
parent
7d7214175b
commit
6990a780e4
|
@ -26,7 +26,7 @@ import itemView from './WikiPageIndexItemView'
|
|||
import template from 'jst/wiki/WikiPageIndex'
|
||||
import StickyHeaderMixin from '../StickyHeaderMixin'
|
||||
import splitAssetString from '../../str/splitAssetString'
|
||||
import ContentTypeExternalToolTray from './ContentTypeExternalToolTray'
|
||||
import ContentTypeExternalToolTray from 'jsx/shared/ContentTypeExternalToolTray'
|
||||
import DirectShareCourseTray from 'jsx/shared/direct_share/DirectShareCourseTray'
|
||||
import DirectShareUserModal from 'jsx/shared/direct_share/DirectShareUserModal'
|
||||
import 'jquery.disableWhileLoading'
|
||||
|
|
|
@ -75,6 +75,8 @@ class ContextModulesController < ApplicationController
|
|||
:root_account => @domain_root_account, :current_user => @current_user).to_a
|
||||
placements.select { |p| @menu_tools[p] = tools.select{|t| t.has_placement? p} }
|
||||
|
||||
@module_index_tools = @domain_root_account&.feature_enabled?(:commons_favorites) ? external_tools_display_hashes(:module_index_menu) : []
|
||||
|
||||
module_file_details = load_module_file_details if @context.grants_right?(@current_user, session, :manage_content)
|
||||
js_env :course_id => @context.id,
|
||||
:CONTEXT_URL_ROOT => polymorphic_path([@context]),
|
||||
|
@ -84,7 +86,8 @@ class ContextModulesController < ApplicationController
|
|||
:MODULE_FILE_PERMISSIONS => {
|
||||
usage_rights_required: @context.usage_rights_required?,
|
||||
manage_files: @context.grants_right?(@current_user, session, :manage_files)
|
||||
}
|
||||
},
|
||||
:MODULE_INDEX_TOOLS => @module_index_tools
|
||||
|
||||
is_master_course = MasterCourses::MasterTemplate.is_master_course?(@context)
|
||||
is_child_course = MasterCourses::ChildSubscription.is_child_course?(@context)
|
||||
|
|
|
@ -44,7 +44,8 @@ module ContextExternalToolsHelper
|
|||
end
|
||||
|
||||
link_attrs = {
|
||||
href: tool[:base_url]
|
||||
:href => tool[:base_url],
|
||||
"data-tool-id" => tool[:id]
|
||||
}
|
||||
|
||||
link_attrs[:class] = options[:link_class] if options[:link_class]
|
||||
|
|
|
@ -93,6 +93,7 @@ export default class ExternalToolPlacementButton extends React.Component {
|
|||
link_selection: I18n.t('Link Selection'),
|
||||
migration_selection: I18n.t('Migration Selection'),
|
||||
module_menu: I18n.t('Module Menu'),
|
||||
module_index_menu: I18n.t('Modules Index Menu'),
|
||||
post_grades: I18n.t('Sync Grades'),
|
||||
quiz_menu: I18n.t('Quiz Menu'),
|
||||
student_context_card: I18n.t('Student Context Card'),
|
||||
|
|
|
@ -34,6 +34,11 @@ const toolShape = shape({
|
|||
icon_url: string
|
||||
})
|
||||
|
||||
const moduleShape = shape({
|
||||
id: string.isRequired,
|
||||
name: string.isRequired
|
||||
})
|
||||
|
||||
const knownResourceTypes = [
|
||||
'assignment',
|
||||
'assignment_group',
|
||||
|
@ -53,7 +58,7 @@ ContentTypeExternalToolTray.propTypes = {
|
|||
acceptedResourceTypes: arrayOf(oneOf(knownResourceTypes)).isRequired,
|
||||
targetResourceType: oneOf(knownResourceTypes).isRequired,
|
||||
allowItemSelection: bool.isRequired,
|
||||
selectableItems: arrayOf(oneOf(knownResourceTypes)).isRequired,
|
||||
selectableItems: arrayOf(moduleShape).isRequired,
|
||||
onDismiss: func,
|
||||
open: bool
|
||||
}
|
|
@ -18,7 +18,7 @@
|
|||
|
||||
import React from 'react'
|
||||
import {render, fireEvent} from '@testing-library/react'
|
||||
import ContentTypeExternalToolTray from 'compiled/views/wiki/ContentTypeExternalToolTray'
|
||||
import ContentTypeExternalToolTray from '../ContentTypeExternalToolTray'
|
||||
|
||||
describe('ContentTypeExternalToolTray', () => {
|
||||
const tool = {id: 1, base_url: 'https://one.lti.com/', title: 'First LTI'}
|
|
@ -52,6 +52,7 @@ module Lti
|
|||
:link_selection,
|
||||
:migration_selection,
|
||||
:module_menu,
|
||||
:module_index_menu,
|
||||
:post_grades,
|
||||
:quiz_menu,
|
||||
:resource_selection,
|
||||
|
|
|
@ -61,6 +61,18 @@
|
|||
<%= t('#context_modules.buttons.add_module', 'Module') %>
|
||||
</button>
|
||||
<% end %>
|
||||
<% if @module_index_tools.present? %>
|
||||
<div class="inline-block module_index_tools">
|
||||
<a class="al-trigger btn" role="button" aria-haspopup="true" aria-owns="toolbar-1" href="#">
|
||||
<i class="icon-more" aria-hidden="true"></i>
|
||||
<span class="screenreader-only"><%= t("Modules Settings") %></span>
|
||||
</a>
|
||||
|
||||
<ul id="toolbar-1" class="al-options" role="menu" aria-hidden="true" aria-expanded="false">
|
||||
<%= external_tools_menu_items(@module_index_tools, {link_class: "menu_tool_link", settings_key: :module_index_menu, in_list: true}) %>
|
||||
</ul>
|
||||
</div>
|
||||
<% end %>
|
||||
</div>
|
||||
<% if @last_web_export %>
|
||||
<small class="muted">
|
||||
|
@ -73,6 +85,7 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div id="external-tool-mount-point"></div>
|
||||
<div class="item-group-container" id="context_modules_sortable_container">
|
||||
<div id="no_context_modules_message" style="display:none;">
|
||||
<% if can_do(@context.context_modules.temp_record, @current_user, :create) %>
|
||||
|
|
|
@ -139,7 +139,7 @@ module Lti
|
|||
collaboration_params
|
||||
when 'homework_submission'
|
||||
homework_submission_params(assignment)
|
||||
when 'wiki_index_menu'
|
||||
when 'wiki_index_menu', 'module_index_menu'
|
||||
{}
|
||||
else
|
||||
# TODO: we _could_, if configured, have any other placements return to the content migration page...
|
||||
|
|
|
@ -1304,17 +1304,20 @@ module Lti
|
|||
USAGE_RIGHTS_GUARD
|
||||
|
||||
# Returns the types of resources that can be imported to the current page, forwarded from the request.
|
||||
# Value is an array of one or more values of: ["assignment", "assignment_group", "audio",
|
||||
# Value is a comma-separated array of one or more values of: ["assignment", "assignment_group", "audio",
|
||||
# "discussion_topic", "document", "image", "module", "quiz", "page", "video"]
|
||||
#
|
||||
# @example
|
||||
# ```
|
||||
# ["page"]
|
||||
# ["module"]
|
||||
# ["assignment", "discussion_topic", "page", "quiz", "module"]
|
||||
# "page"
|
||||
# "module"
|
||||
# "assignment,discussion_topic,page,quiz,module"
|
||||
# ```
|
||||
register_expansion 'com.instructure.Course.accept_canvas_resource_types', [],
|
||||
-> { @request.parameters['com_instructure_course_accept_canvas_resource_types'] },
|
||||
-> {
|
||||
val = @request.parameters['com_instructure_course_accept_canvas_resource_types']
|
||||
val.is_a?(Array) ? val.join(",") : val
|
||||
},
|
||||
default_name: 'com_instructure_course_accept_canvas_resource_types'
|
||||
|
||||
# Returns the target resource type for the current page, forwarded from the request.
|
||||
|
|
|
@ -40,6 +40,9 @@ import Publishable from 'compiled/models/Publishable'
|
|||
import PublishButtonView from 'compiled/views/PublishButtonView'
|
||||
import htmlEscape from './str/htmlEscape'
|
||||
import setupContentIds from 'jsx/modules/utils/setupContentIds'
|
||||
import ContentTypeExternalToolTray from 'jsx/shared/ContentTypeExternalToolTray'
|
||||
import {ltiState} from './lti/post_message/handleLtiPostMessage'
|
||||
import {monitorLtiMessages} from 'lti/messages'
|
||||
import get from 'lodash/get'
|
||||
import axios from 'axios'
|
||||
import {showFlashError} from 'jsx/shared/FlashAlert'
|
||||
|
@ -2500,6 +2503,63 @@ $(document).ready(function() {
|
|||
$contextModules.each(function() {
|
||||
modules.updateProgressionState($(this))
|
||||
})
|
||||
|
||||
function setExternalToolTray(tool, returnFocusTo) {
|
||||
const handleDismiss = () => {
|
||||
setExternalToolTray(null)
|
||||
returnFocusTo.focus()
|
||||
if (ltiState?.tray?.refreshOnClose) {
|
||||
window.location.reload()
|
||||
}
|
||||
}
|
||||
|
||||
const moduleData = []
|
||||
$('#context_modules .context_module').each(function() {
|
||||
moduleData.push({
|
||||
id: $(this)
|
||||
.attr('id')
|
||||
.substring('context_module_'.length),
|
||||
name: $(this)
|
||||
.find('.name')
|
||||
.attr('title')
|
||||
})
|
||||
})
|
||||
|
||||
ReactDOM.render(
|
||||
<ContentTypeExternalToolTray
|
||||
tool={tool}
|
||||
placement="module_index_menu"
|
||||
acceptedResourceTypes={[
|
||||
'assignment',
|
||||
'audio',
|
||||
'discussion_topic',
|
||||
'document',
|
||||
'image',
|
||||
'module',
|
||||
'quiz',
|
||||
'page',
|
||||
'video'
|
||||
]}
|
||||
targetResourceType="module"
|
||||
allowItemSelection
|
||||
selectableItems={moduleData}
|
||||
onDismiss={handleDismiss}
|
||||
open={tool !== null}
|
||||
/>,
|
||||
$('#external-tool-mount-point')[0]
|
||||
)
|
||||
}
|
||||
|
||||
function openExternalTool(ev) {
|
||||
if (ev != null) {
|
||||
ev.preventDefault()
|
||||
}
|
||||
const tool = ENV.MODULE_INDEX_TOOLS.find(t => t.id === ev.target.dataset.toolId)
|
||||
setExternalToolTray(tool, $('.al-trigger')[0])
|
||||
}
|
||||
|
||||
$('.module_index_tools .menu_tool_link').click(openExternalTool)
|
||||
monitorLtiMessages()
|
||||
})
|
||||
|
||||
export default modules
|
||||
|
|
|
@ -545,6 +545,7 @@ describe ExternalToolsController, type: :request do
|
|||
et.discussion_topic_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "discussion topic menu", display_type: 'full_width', visibility: 'admins'}
|
||||
et.file_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "module menu", display_type: 'full_width', visibility: 'admins'}
|
||||
et.module_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "module menu", display_type: 'full_width', visibility: 'admins'}
|
||||
et.module_index_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "modules index menu", display_type: 'full_width', visibility: 'admins'}
|
||||
et.quiz_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "quiz menu", display_type: 'full_width', visibility: 'admins'}
|
||||
et.wiki_page_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "wiki page menu", display_type: 'full_width', visibility: 'admins'}
|
||||
et.wiki_index_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "wiki index menu", display_type: 'full_width', visibility: 'admins'}
|
||||
|
@ -722,6 +723,14 @@ describe ExternalToolsController, type: :request do
|
|||
"display_type"=>'full_width',
|
||||
"selection_height"=>400,
|
||||
"selection_width"=>800},
|
||||
"module_index_menu"=>
|
||||
{"text"=>"modules index menu",
|
||||
"label"=>"modules index menu",
|
||||
"url"=>"http://www.example.com/ims/lti/resource",
|
||||
"visibility"=>'admins',
|
||||
"display_type"=>'full_width',
|
||||
"selection_height"=>400,
|
||||
"selection_width"=>800},
|
||||
"quiz_menu"=>
|
||||
{"text"=>"quiz menu",
|
||||
"label"=>"quiz menu",
|
||||
|
|
|
@ -675,7 +675,7 @@ module Lti
|
|||
it 'has substitution for $com.instructure.Course.accept_canvas_resource_types' do
|
||||
exp_hash = {test: '$com.instructure.Course.accept_canvas_resource_types'}
|
||||
variable_expander.expand_variables!(exp_hash)
|
||||
expect(exp_hash[:test]).to eq ["page", "module"]
|
||||
expect(exp_hash[:test]).to eq "page,module"
|
||||
end
|
||||
|
||||
it 'has substitution for $com.instructure.Course.canvas_resource_type' do
|
||||
|
|
|
@ -168,4 +168,41 @@ describe "context modules" do
|
|||
expect(link).not_to be_displayed
|
||||
end
|
||||
end
|
||||
|
||||
context "module index tool placement" do
|
||||
before do
|
||||
course_with_teacher_logged_in
|
||||
|
||||
@tool = Account.default.context_external_tools.new(:name => "a", :domain => "google.com", :consumer_key => '12345', :shared_secret => 'secret')
|
||||
@tool.module_index_menu = {:url => "http://www.example.com", :text => "Import Stuff"}
|
||||
@tool.save!
|
||||
@module1 = @course.context_modules.create!(:name => "module1")
|
||||
@module2 = @course.context_modules.create!(:name => "module2")
|
||||
|
||||
Account.default.enable_feature!(:commons_favorites)
|
||||
end
|
||||
|
||||
it "should be able to launch the index menu tool via the tray", custom_timeout: 60 do
|
||||
get "/courses/#{@course.id}/modules"
|
||||
|
||||
gear = f(".header-bar .al-trigger")
|
||||
gear.click
|
||||
tool_link = f(".header-bar li.ui-menu-item a.menu_tool_link")
|
||||
expect(tool_link).to include_text("Import Stuff")
|
||||
|
||||
tool_link.click
|
||||
wait_for_ajaximations
|
||||
tray = f("[role='dialog']")
|
||||
expect(tray['aria-label']).to eq "Import Stuff"
|
||||
iframe = tray.find_element(:css, "iframe")
|
||||
expect(iframe['src']).to include("/courses/#{@course.id}/external_tools/#{@tool.id}")
|
||||
query_params = Rack::Utils.parse_nested_query(URI.parse(iframe['src']).query)
|
||||
expect(query_params["launch_type"]).to eq "module_index_menu"
|
||||
expect(query_params["com_instructure_course_allow_canvas_resource_selection"]).to eq "true"
|
||||
expect(query_params["com_instructure_course_accept_canvas_resource_types"]).to match_array(
|
||||
["assignment", "audio", "discussion_topic", "document", "image", "module", "quiz", "page", "video"])
|
||||
module_data = [@module1, @module2].map{|m| {"id" => m.id.to_s, "name" => m.name}}
|
||||
expect(query_params["com_instructure_course_available_canvas_resources"].values).to match_array(module_data)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
Loading…
Reference in New Issue