add file_menu external tools to new files cog menu
test plan: * add an external tool (see the example xml file referenced in the ticket) configured for the file_menu * with new files enabled, the external tool should add an item in the cog menu for files * if the file's content-type is unrecognized by the tool (i.e. not a standard document/image/video file), the item should be disabled * the external tool should also add an item to the cog menu for file module items on the modules page closes #CNVS-17005 Change-Id: I8a5497be2f784d5fc64969baf30e33ae53b5dc1a Reviewed-on: https://gerrit.instructure.com/45203 Reviewed-by: Jeremy Stanley <jeremy@instructure.com> Tested-by: Jenkins <jenkins@instructure.com> QA-Review: Jahnavi Yetukuri <jyetukuri@instructure.com> Product-Review: James Williams <jamesw@instructure.com>
This commit is contained in:
parent
96da5de2d8
commit
96765144c9
|
@ -68,3 +68,8 @@ define [
|
|||
present: ->
|
||||
_.clone(@attributes)
|
||||
|
||||
externalToolEnabled: (tool) =>
|
||||
if tool.accept_media_types && tool.accept_media_types.length > 0
|
||||
_.indexOf(tool.accept_media_types.split(","), @get('content-type')) != -1
|
||||
else
|
||||
true
|
||||
|
|
|
@ -74,6 +74,7 @@ define [
|
|||
|
||||
userCanManageFilesForContext = filesEnv.userHasPermission({contextType: contextType, contextId: contextId}, 'manage_files')
|
||||
usageRightsRequiredForContext = filesEnv.contextsDictionary["#{contextType}_#{contextId}"]?.usage_rights_required
|
||||
externalToolsForContext = filesEnv.contextFor({contextType: contextType, contextId: contextId})?.file_menu_tools || []
|
||||
|
||||
div null,
|
||||
# For whatever reason, VO in Safari didn't like just the h1 tag.
|
||||
|
@ -129,6 +130,7 @@ define [
|
|||
areAllItemsSelected: @areAllItemsSelected
|
||||
userCanManageFilesForContext: userCanManageFilesForContext
|
||||
usageRightsRequiredForContext: usageRightsRequiredForContext
|
||||
externalToolsForContext: externalToolsForContext
|
||||
previewItem: @previewItem
|
||||
dndOptions:
|
||||
onItemDragStart: @onItemDragStart
|
||||
|
|
|
@ -173,4 +173,5 @@ define [
|
|||
startEditingName: @startEditingName
|
||||
userCanManageFilesForContext: @props.userCanManageFilesForContext
|
||||
usageRightsRequiredForContext: @props.usageRightsRequiredForContext
|
||||
externalToolsForContext: @props.externalToolsForContext
|
||||
})
|
||||
|
|
|
@ -5,6 +5,7 @@ define [
|
|||
'compiled/fn/preventDefault'
|
||||
'../modules/customPropTypes'
|
||||
'../modules/filesEnv'
|
||||
'compiled/models/File'
|
||||
'compiled/models/Folder'
|
||||
'./RestrictedDialogForm'
|
||||
'../utils/openMoveDialog'
|
||||
|
@ -13,7 +14,7 @@ define [
|
|||
'../utils/deleteStuff'
|
||||
'jquery'
|
||||
'jqueryui/dialog'
|
||||
], (I18n, React, withReactDOM, preventDefault, customPropTypes, filesEnv, Folder, RestrictedDialogForm, openMoveDialog, openUsageRightsDialog, downloadStuffAsAZip, deleteStuff, $) ->
|
||||
], (I18n, React, withReactDOM, preventDefault, customPropTypes, filesEnv, File, Folder, RestrictedDialogForm, openMoveDialog, openUsageRightsDialog, downloadStuffAsAZip, deleteStuff, $) ->
|
||||
|
||||
ItemCog = React.createClass
|
||||
displayName: 'ItemCog'
|
||||
|
@ -22,6 +23,24 @@ define [
|
|||
model: customPropTypes.filesystemObject
|
||||
|
||||
render: withReactDOM ->
|
||||
if @props.model instanceof File
|
||||
externalToolMenuItems = @props.externalToolsForContext.map (tool) =>
|
||||
if @props.model.externalToolEnabled(tool)
|
||||
li {},
|
||||
a {
|
||||
href: "#{tool.base_url}&files[]=#{@props.model.id}",
|
||||
},
|
||||
tool.title
|
||||
else
|
||||
li {},
|
||||
a {
|
||||
className: "disabled",
|
||||
href: "#"
|
||||
},
|
||||
tool.title
|
||||
else
|
||||
externalToolMenuItems = []
|
||||
|
||||
wrap = (fn) =>
|
||||
preventDefault (event) =>
|
||||
singularContextType = @props.model.collection?.parentFolder?.get('context_type').toLowerCase()
|
||||
|
@ -91,4 +110,4 @@ define [
|
|||
ref: 'deleteLink'
|
||||
},
|
||||
I18n.t('delete', 'Delete')
|
||||
]
|
||||
].concat(externalToolMenuItems)
|
||||
|
|
|
@ -103,6 +103,7 @@ define [
|
|||
isSelected: child in @props.selectedItems
|
||||
toggleSelected: @props.toggleItemSelected.bind(null, child)
|
||||
userCanManageFilesForContext: @props.userCanManageFilesForContext
|
||||
externalToolsForContext: @props.externalToolsForContext
|
||||
previewItem: @props.previewItem.bind(null, child)
|
||||
dndOptions: @props.dndOptions
|
||||
LoadingIndicator isLoading: !@state.collection.loadedAll
|
||||
|
|
|
@ -113,6 +113,7 @@ define [
|
|||
toggleSelected: @props.toggleItemSelected.bind(null, child)
|
||||
userCanManageFilesForContext: @props.userCanManageFilesForContext
|
||||
usageRightsRequiredForContext: @props.usageRightsRequiredForContext
|
||||
externalToolsForContext: @props.externalToolsForContext
|
||||
previewItem: @props.previewItem.bind(null, child)
|
||||
dndOptions: @props.dndOptions
|
||||
|
||||
|
|
|
@ -31,16 +31,20 @@ define [
|
|||
folder.fetch()
|
||||
folder
|
||||
|
||||
filesEnv.userHasPermission = (folderOrFile, action) ->
|
||||
return false unless folderOrFile
|
||||
|
||||
filesEnv.contextFor = (folderOrFile) ->
|
||||
if folderOrFile.collection?.parentFolder
|
||||
folderOrFile = folderOrFile.collection.parentFolder
|
||||
if folderOrFile instanceof Folder
|
||||
folder = folderOrFile
|
||||
assetString = (folder?.get('context_type') + 's_' + folder?.get('context_id')).toLowerCase()
|
||||
else if folderOrFile.contextType and folderOrFile.contextId
|
||||
assetString = "#{folderOrFile.contextType}_#{folderOrFile.contextId}".toLowerCase()
|
||||
filesEnv.contextsDictionary?[assetString]
|
||||
|
||||
filesEnv.contextsDictionary?[assetString]?.permissions?[action]
|
||||
filesEnv.userHasPermission = (folderOrFile, action) ->
|
||||
return false unless folderOrFile
|
||||
|
||||
filesEnv.contextFor(folderOrFile)?.permissions?[action]
|
||||
|
||||
filesEnv.baseUrl = if filesEnv.showingAllContexts
|
||||
'/files'
|
||||
|
|
|
@ -30,6 +30,7 @@ define [
|
|||
{extension_type: 'discussion_topic_menu', text: I18n.t 'discussion_topic_menu_configured', 'Discussion Topic menu configured'}
|
||||
{extension_type: 'module_menu', text: I18n.t 'module_menu_configured', 'Module menu configured'}
|
||||
{extension_type: 'quiz_menu', text: I18n.t 'quiz_menu_configured', 'Quiz menu configured'}
|
||||
{extension_type: 'file_menu', text: I18n.t 'file_menu_configured', 'File menu configured'}
|
||||
{extension_type: 'wiki_page_menu', text: I18n.t 'wiki_page_menu_configured', 'Wiki page menu configured'}
|
||||
]
|
||||
|
||||
|
|
|
@ -131,15 +131,21 @@ class ApplicationController < ActionController::Base
|
|||
end
|
||||
helper_method :js_env
|
||||
|
||||
def external_tools_display_hashes(type)
|
||||
tools = ContextExternalTool.all_tools_for(@context, :type => type,
|
||||
def external_tools_display_hashes(type, context=@context, custom_settings=[])
|
||||
context = context.account if context.is_a?(User)
|
||||
tools = ContextExternalTool.all_tools_for(context, :type => type,
|
||||
:root_account => @domain_root_account, :current_user => @current_user)
|
||||
|
||||
extension_settings = [:icon_url] + custom_settings
|
||||
tools.map do |tool|
|
||||
{
|
||||
hash = {
|
||||
:title => tool.label_for(type),
|
||||
:icon_url => tool.extension_setting(type, :icon_url),
|
||||
:base_url => course_external_tool_path(@context, tool, :launch_type => type)
|
||||
:base_url => named_context_url(context, :context_external_tool_path, tool, :launch_type => type)
|
||||
}
|
||||
extension_settings.each do |setting|
|
||||
hash[setting] = tool.extension_setting(type, setting)
|
||||
end
|
||||
hash
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ class ContextModulesController < ApplicationController
|
|||
@collapsed_modules = ContextModuleProgression.for_user(@current_user).for_modules(@modules).select([:context_module_id, :collapsed]).select{|p| p.collapsed? }.map(&:context_module_id)
|
||||
|
||||
@menu_tools = {}
|
||||
[:assignment_menu, :module_menu, :quiz_menu, :wiki_page_menu].each do |type|
|
||||
[:assignment_menu, :discussion_topic_menu, :file_menu, :module_menu, :quiz_menu, :wiki_page_menu].each do |type|
|
||||
@menu_tools[type] = ContextExternalTool.all_tools_for(@context, :type => type,
|
||||
:root_account => @domain_root_account, :current_user => @current_user)
|
||||
end
|
||||
|
|
|
@ -461,12 +461,16 @@ class ExternalToolsController < ApplicationController
|
|||
protected :content_item_selection_response
|
||||
|
||||
def content_item_response
|
||||
#contstruct query params for the export endpoint
|
||||
export_type = params["export_type"] || "common_cartridge"
|
||||
content_items = []
|
||||
|
||||
if export_type == "common_cartridge"
|
||||
content_items << content_item_for_common_cartridge
|
||||
if params[:files].present?
|
||||
content_items << content_item_for_file
|
||||
else
|
||||
#construct query params for the export endpoint
|
||||
export_type = params["export_type"] || "common_cartridge"
|
||||
if export_type == "common_cartridge"
|
||||
content_items << content_item_for_common_cartridge
|
||||
end
|
||||
end
|
||||
|
||||
{
|
||||
|
@ -476,6 +480,30 @@ class ExternalToolsController < ApplicationController
|
|||
end
|
||||
protected :content_item_response
|
||||
|
||||
def content_item_for_file
|
||||
#find the content title
|
||||
file = Attachment.where(:id => params[:files].first).first
|
||||
if @context.is_a?(Account)
|
||||
raise ActiveRecord::RecordNotFound unless file.context == @current_user
|
||||
elsif file.context.is_a?(Course)
|
||||
raise ActiveRecord::RecordNotFound unless file.context == @context
|
||||
elsif file.context.is_a?(Group)
|
||||
raise ActiveRecord::RecordNotFound unless file.context.context == @context
|
||||
end
|
||||
render_unauthorized_action if file.locked_for?(@current_user, check_policies: true)
|
||||
|
||||
{
|
||||
"@type" => "ContentItemPlacement",
|
||||
"placementOf" => {
|
||||
"@type" => "FileItem",
|
||||
"@id" => file_download_url(file, { :verifier => file.uuid, :download => '1', :download_frd => '1' }),
|
||||
"mediaType" => file.content_type,
|
||||
"title" => file.display_name
|
||||
}
|
||||
}
|
||||
end
|
||||
protected :content_item_for_file
|
||||
|
||||
def content_item_for_common_cartridge
|
||||
query_params = {"export_type" => "common_cartridge"}
|
||||
|
||||
|
|
|
@ -326,13 +326,23 @@ class FilesController < ApplicationController
|
|||
@contexts = [@context]
|
||||
get_all_pertinent_contexts(include_groups: true) if @context == @current_user
|
||||
files_contexts = @contexts.map do |context|
|
||||
|
||||
tool_context = if context.is_a?(Course)
|
||||
context
|
||||
elsif context.is_a?(User)
|
||||
@domain_root_account
|
||||
elsif context.is_a?(Group)
|
||||
context.context
|
||||
end
|
||||
file_menu_tools = (tool_context ? external_tools_display_hashes(:file_menu, tool_context, [:accept_media_types]) : [])
|
||||
{
|
||||
asset_string: context.asset_string,
|
||||
name: context == @current_user ? t('my_files', 'My Files') : context.name,
|
||||
usage_rights_required: context.feature_enabled?(:usage_rights_required),
|
||||
permissions: {
|
||||
manage_files: context.grants_right?(@current_user, session, :manage_files),
|
||||
}
|
||||
},
|
||||
file_menu_tools: file_menu_tools
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -340,7 +350,9 @@ class FilesController < ApplicationController
|
|||
@body_classes << 'full-width padless-content'
|
||||
js_bundle :react_files
|
||||
jammit_css :react_files
|
||||
js_env :FILES_CONTEXTS => files_contexts
|
||||
js_env({
|
||||
:FILES_CONTEXTS => files_contexts
|
||||
})
|
||||
|
||||
render :text => "".html_safe, :layout => true
|
||||
end
|
||||
|
|
|
@ -46,9 +46,11 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
:user_navigation, :course_navigation, :account_navigation, :resource_selection,
|
||||
:editor_button, :homework_submission, :migration_selection, :course_home_sub_navigation,
|
||||
:course_settings_sub_navigation, :global_navigation,
|
||||
:assignment_menu, :discussion_topic_menu, :module_menu, :quiz_menu, :wiki_page_menu
|
||||
:assignment_menu, :file_menu, :discussion_topic_menu, :module_menu, :quiz_menu, :wiki_page_menu
|
||||
]
|
||||
|
||||
CUSTOM_EXTENSION_KEYS = {:file_menu => [:accept_media_types]}
|
||||
|
||||
EXTENSION_TYPES.each do |type|
|
||||
class_eval <<-RUBY, __FILE__, __LINE__ + 1
|
||||
def #{type}(setting=nil)
|
||||
|
@ -79,6 +81,9 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
|
||||
extension_keys = [:custom_fields, :default, :display_type, :enabled, :icon_url,
|
||||
:selection_height, :selection_width, :text, :url, :message_type]
|
||||
if custom_keys = CUSTOM_EXTENSION_KEYS[type]
|
||||
extension_keys += custom_keys
|
||||
end
|
||||
extension_keys += {
|
||||
:visibility => lambda{|v| %w{members admins}.include?(v)}
|
||||
}.to_a
|
||||
|
@ -452,7 +457,8 @@ class ContextExternalTool < ActiveRecord::Base
|
|||
private_class_method :contexts_to_search
|
||||
|
||||
LOR_TYPES = [:course_home_sub_navigation, :course_settings_sub_navigation, :global_navigation,
|
||||
:assignment_menu, :discussion_topic_menu, :module_menu, :quiz_menu, :wiki_page_menu]
|
||||
:assignment_menu, :file_menu, :discussion_topic_menu, :module_menu, :quiz_menu,
|
||||
:wiki_page_menu]
|
||||
def self.all_tools_for(context, options={})
|
||||
#options[:type] is deprecated, use options[:placements] instead
|
||||
placements =* options[:placements] || options[:type]
|
||||
|
|
|
@ -447,6 +447,14 @@ $context_module_bg_color: #f2f3f4
|
|||
li.assignment_menu
|
||||
display: none
|
||||
|
||||
.context_module_item:not(.discussion_topic)
|
||||
li.discussion_topic_menu
|
||||
display: none
|
||||
|
||||
.context_module_item:not(.attachment)
|
||||
li.file_menu
|
||||
display: none
|
||||
|
||||
.context_module_item:not(.quiz)
|
||||
li.quiz_menu
|
||||
display: none
|
||||
|
|
|
@ -149,6 +149,8 @@
|
|||
<li><a href="<%= context_url(@context, :context_url) %>/modules/items/<%= tag ? tag.id : "{{ id }}" %>" class="delete_item_link delete_link" title="<%= t('links.remove_item_from_module', %{Remove this item from the module}) %>"><%= @module_item_image_tags['delete'] %> <%= t('links.remove_item', %{Remove}) %></a></li>
|
||||
<% menu_type_to_class = {
|
||||
:assignment_menu => Assignment,
|
||||
:discussion_topic_menu => DiscussionTopic,
|
||||
:file_menu => Attachment,
|
||||
:quiz_menu => Quizzes::Quiz,
|
||||
:wiki_page_menu => WikiPage
|
||||
}
|
||||
|
|
|
@ -419,6 +419,7 @@ describe ExternalToolsController, type: :request do
|
|||
et.global_navigation = {:url=>"http://www.example.com/ims/lti/resource", :text => "global navigation", display_type: 'full_width', visibility: 'admins'}
|
||||
et.assignment_menu = {:url=>"http://www.example.com/ims/lti/resource", :text => "assignment menu", display_type: 'full_width', visibility: 'admins'}
|
||||
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.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'}
|
||||
|
@ -572,6 +573,14 @@ describe ExternalToolsController, type: :request do
|
|||
"display_type"=>'full_width',
|
||||
"selection_height"=>400,
|
||||
"selection_width"=>800},
|
||||
"file_menu"=>
|
||||
{"text"=>"module menu",
|
||||
"label"=>"module menu",
|
||||
"url"=>"http://www.example.com/ims/lti/resource",
|
||||
"visibility"=>'admins',
|
||||
"display_type"=>'full_width',
|
||||
"selection_height"=>400,
|
||||
"selection_width"=>800},
|
||||
"module_menu"=>
|
||||
{"text"=>"module menu",
|
||||
"label"=>"module menu",
|
||||
|
|
|
@ -130,6 +130,18 @@ describe ExternalToolsController do
|
|||
expect(placement['placementOf']['title']).to eq 'blah'
|
||||
end
|
||||
|
||||
it "sends content item json for a file" do
|
||||
user_session(@teacher)
|
||||
attachment_model
|
||||
get :show, :course_id => @course.id, id: @tool.id, :files => [@attachment.id]
|
||||
placement = JSON.parse(assigns[:lti_launch].params['content_items'])['@graph'].first
|
||||
download_url = placement['placementOf']['@id']
|
||||
|
||||
expect(download_url).to include(@attachment.uuid)
|
||||
expect(placement['placementOf']['mediaType']).to eq @attachment.content_type
|
||||
expect(placement['placementOf']['title']).to eq @attachment.display_name
|
||||
end
|
||||
|
||||
it "sends content item json for a quiz" do
|
||||
user_session(@teacher)
|
||||
quiz = @course.quizzes.create!(title: 'a quiz')
|
||||
|
|
|
@ -118,6 +118,19 @@ describe ContextExternalTool do
|
|||
expect(@tool.has_placement?(:course_navigation)).to eq true
|
||||
end
|
||||
|
||||
it "should allow accept_media_types setting exclusively for file_menu extension" do
|
||||
@tool = @course.context_external_tools.create!(:name => "a", :url => "http://google.com", :consumer_key => '12345', :shared_secret => 'secret')
|
||||
@tool.course_navigation = {
|
||||
:accept_media_types => "types"
|
||||
}
|
||||
@tool.file_menu = {
|
||||
:accept_media_types => "types"
|
||||
}
|
||||
@tool.save!
|
||||
expect(@tool.extension_setting(:course_navigation, :accept_media_types)).to be_blank
|
||||
expect(@tool.extension_setting(:file_menu, :accept_media_types)).to eq "types"
|
||||
end
|
||||
|
||||
it "should clear disabled extensions" do
|
||||
@tool = @course.context_external_tools.create!(:name => "a", :url => "http://google.com", :consumer_key => '12345', :shared_secret => 'secret')
|
||||
@tool.course_navigation = {
|
||||
|
|
|
@ -184,6 +184,11 @@ shared_examples_for "external tools tests" do
|
|||
<lticm:property name="text">Build/Link to Wiki Page</lticm:property>
|
||||
<lticm:property name="display_type">full_width</lticm:property>
|
||||
</lticm:options>
|
||||
<lticm:options name="file_menu">
|
||||
<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>
|
||||
<lticm:options name="module_menu">
|
||||
<lticm:property name="url">https://example.com/wiki</lticm:property>
|
||||
<lticm:property name="text">Build/Link to Wiki Page</lticm:property>
|
||||
|
|
Loading…
Reference in New Issue