Add group files to the rce
closes: LS-1392 flag=rce_enhancements test plan: prereqs: 1) if you plan on testing group media, you'll need g/247879 built into your RCS when testing this. 2)rce in a group in a course - /course/:id/groups - +Group Set - +Group - add student(s) - from the group's kabob menu, select "Visit Group Home Page" - +Announcement - +Announcement again - TADA, you're in an RCE in a group context - on the toolbar, Images > Upload Image and upload an image - on the toolbar, Images > Group Images > expect the image you just uploaded to be listed - click it > expect it to be embedded in the RCE - repeat for Documents - repeat for Media Change-Id: I686fe4c42df32cb7e767fe2e277530cb472b2fd6 Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/247738 Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Reviewed-by: Jeremy Stanley <jeremy@instructure.com> QA-Review: Robin Kuss <rkuss@instructure.com> Product-Review: Peyton Craighill <pcraighill@instructure.com>
This commit is contained in:
parent
b1677c838f
commit
a204b384dc
|
@ -71,8 +71,8 @@
|
|||
class MediaObjectsController < ApplicationController
|
||||
include Api::V1::MediaObject
|
||||
|
||||
before_action :load_media_object, :except => [:index, :update_media_object]
|
||||
before_action :require_user, :except => [:show, :iframe_media_player]
|
||||
before_action :load_media_object, except: %i[index update_media_object]
|
||||
before_action :require_user, except: %i[show iframe_media_player]
|
||||
|
||||
# @{not an}API Show Media Object Details
|
||||
# This isn't an API because it needs to work for non-logged in users (video in public course)
|
||||
|
@ -85,7 +85,7 @@ class MediaObjectsController < ApplicationController
|
|||
#
|
||||
# @returns MediaObject
|
||||
def show
|
||||
render :json => media_object_api_json(@media_object, @current_user, session)
|
||||
render json: media_object_api_json(@media_object, @current_user, session)
|
||||
end
|
||||
|
||||
# @API List Media Objects
|
||||
|
@ -121,31 +121,41 @@ class MediaObjectsController < ApplicationController
|
|||
# @returns [MediaObject]
|
||||
def index
|
||||
if params[:course_id]
|
||||
course = Course.find(params[:course_id])
|
||||
root_folder = Folder.root_folders(course).first
|
||||
context = Course.find(params[:course_id])
|
||||
url = api_v1_course_media_objects_url
|
||||
elsif params[:group_id]
|
||||
context = Group.find(params[:group_id])
|
||||
url = api_v1_group_media_objects_url
|
||||
end
|
||||
if context
|
||||
root_folder = Folder.root_folders(context).first
|
||||
|
||||
if root_folder.grants_right?(@current_user, :read_contents)
|
||||
# if the user has access to the course's root folder, let's
|
||||
# assume they have access to the course's media, even if it's
|
||||
# if the user has access to the context's root folder, let's
|
||||
# assume they have access to the context's media, even if it's
|
||||
# media not associated with an Attachment in there
|
||||
scope = MediaObject.active.where(:context => course)
|
||||
url = api_v1_course_media_objects_url
|
||||
scope = MediaObject.active.where(context: context)
|
||||
else
|
||||
return render_unauthorized_action # not allowed to view files in the course
|
||||
return render_unauthorized_action # not allowed to view files in the context
|
||||
end
|
||||
else
|
||||
scope = MediaObject.active.where(context: @current_user)
|
||||
url = api_v1_media_objects_url
|
||||
end
|
||||
|
||||
order_dir = params[:order] == "desc" ? "desc" : "asc"
|
||||
order_by = params[:sort] || "title"
|
||||
order_by = MediaObject.best_unicode_collation_key('COALESCE(user_entered_title, title)') if order_by == "title"
|
||||
order_dir = params[:order] == 'desc' ? 'desc' : 'asc'
|
||||
order_by = params[:sort] || 'title'
|
||||
if order_by == 'title'
|
||||
order_by = MediaObject.best_unicode_collation_key('COALESCE(user_entered_title, title)')
|
||||
end
|
||||
scope = scope.order(order_by => order_dir)
|
||||
|
||||
exclude = params[:exclude] || []
|
||||
media_objects = Api.paginate(scope, self, url).
|
||||
map{ |mo| media_object_api_json(mo, @current_user, session, exclude)}
|
||||
render :json => media_objects
|
||||
media_objects =
|
||||
Api.paginate(scope, self, url).map do |mo|
|
||||
media_object_api_json(mo, @current_user, session, exclude)
|
||||
end
|
||||
render json: media_objects
|
||||
end
|
||||
|
||||
# @API Update Media Object
|
||||
|
@ -153,20 +163,28 @@ class MediaObjectsController < ApplicationController
|
|||
# @argument user_entered_title [String] The new title.
|
||||
#
|
||||
def update_media_object
|
||||
# media objects don't have any permissions associated with them,
|
||||
# so we just check that this is the user's media
|
||||
|
||||
if params[:media_object_id]
|
||||
@media_object = MediaObject.by_media_id(params[:media_object_id]).first
|
||||
|
||||
return render_unauthorized_action unless @media_object
|
||||
return render_unauthorized_action unless @current_user&.id
|
||||
# media objects don't have any permissions associated with them,
|
||||
# so we just check that this is the user's media
|
||||
|
||||
return render_unauthorized_action unless @media_object.user_id == @current_user.id
|
||||
return render json: {message: "The user_entered_title parameter must have a value"}, status: :bad_request if params[:user_entered_title].blank?
|
||||
if params[:user_entered_title].blank?
|
||||
return(
|
||||
render json: { message: 'The user_entered_title parameter must have a value' },
|
||||
status: :bad_request
|
||||
)
|
||||
end
|
||||
|
||||
self.extend TextHelper
|
||||
@media_object.user_entered_title = CanvasTextHelper.truncate_text(params[:user_entered_title], :max_length => 255)
|
||||
@media_object.user_entered_title =
|
||||
CanvasTextHelper.truncate_text(params[:user_entered_title], max_length: 255)
|
||||
@media_object.save!
|
||||
render :json => media_object_api_json(@media_object, @current_user, session, ["sources", "tracks"])
|
||||
render json: media_object_api_json(@media_object, @current_user, session, %w[sources tracks])
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -177,7 +195,8 @@ class MediaObjectsController < ApplicationController
|
|||
js_env media_object: media_object_api_json(@media_object, @current_user, session)
|
||||
js_bundle :media_player_iframe_content
|
||||
css_bundle :media_player
|
||||
render html: "<div id='player_container'>#{I18n.t('Loading...')}</div>".html_safe, layout: 'layouts/bare'
|
||||
render html: "<div id='player_container'>#{I18n.t('Loading...')}</div>".html_safe,
|
||||
layout: 'layouts/bare'
|
||||
end
|
||||
|
||||
private
|
||||
|
@ -188,10 +207,11 @@ class MediaObjectsController < ApplicationController
|
|||
# Unfortunately, we don't have media_object entities created for everything,
|
||||
# so we use this opportunity to create the object if it does not exist.
|
||||
@media_object = MediaObject.create_if_id_exists(params[:media_object_id])
|
||||
@media_object.send_later_enqueue_args(:retrieve_details, {
|
||||
:singleton => "retrieve_media_details:#{@media_object.media_id}"
|
||||
})
|
||||
increment_request_cost(Setting.get("missed_media_additional_request_cost", "200").to_i)
|
||||
@media_object.send_later_enqueue_args(
|
||||
:retrieve_details,
|
||||
{ singleton: "retrieve_media_details:#{@media_object.media_id}" }
|
||||
)
|
||||
increment_request_cost(Setting.get('missed_media_additional_request_cost', '200').to_i)
|
||||
end
|
||||
|
||||
@media_object.viewed!
|
||||
|
|
|
@ -1011,6 +1011,7 @@ CanvasRails::Application.routes.draw do
|
|||
get 'courses/:course_id/folders/:id', controller: :folders, action: :show, as: 'course_folder'
|
||||
get 'media_objects', controller: 'media_objects', action: :index, as: :media_objects
|
||||
get 'courses/:course_id/media_objects', controller: 'media_objects', action: :index, as: :course_media_objects
|
||||
get 'groups/:group_id/media_objects', controller: 'media_objects', action: :index, as: :group_media_objects
|
||||
put 'accounts/:account_id/courses', action: :batch_update
|
||||
post 'courses/:course_id/ping', action: :ping, as: 'course_ping'
|
||||
|
||||
|
|
|
@ -23,6 +23,7 @@ import {isOKToLink} from '../../contentInsertionUtils'
|
|||
|
||||
const COURSE_PLUGIN_KEY = 'course_documents'
|
||||
const USER_PLUGIN_KEY = 'user_documents'
|
||||
const GROUP_PLUGIN_KEY = 'group_documents'
|
||||
|
||||
function getMenuItems(ed) {
|
||||
const contextType = ed.settings.canvas_rce_user_context.type
|
||||
|
@ -37,6 +38,11 @@ function getMenuItems(ed) {
|
|||
text: formatMessage('Course Documents'),
|
||||
value: 'instructure_course_document'
|
||||
})
|
||||
} else if (contextType === 'group') {
|
||||
items.push({
|
||||
text: formatMessage('Group Documents'),
|
||||
value: 'instructure_group_document'
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
text: formatMessage('User Documents'),
|
||||
|
@ -58,6 +64,10 @@ function doMenuItem(ed, value) {
|
|||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForDocuments', false, USER_PLUGIN_KEY)
|
||||
break
|
||||
case 'instructure_group_document':
|
||||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForDocuments', false, GROUP_PLUGIN_KEY)
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -25,6 +25,7 @@ import clickCallback from './clickCallback'
|
|||
|
||||
const COURSE_PLUGIN_KEY = 'course_images'
|
||||
const USER_PLUGIN_KEY = 'user_images'
|
||||
const GROUP_PLUGIN_KEY = 'group_images'
|
||||
|
||||
const trayController = new TrayController()
|
||||
|
||||
|
@ -41,6 +42,11 @@ function getMenuItems(ed) {
|
|||
text: formatMessage('Course Images'),
|
||||
value: 'instructure_course_image'
|
||||
})
|
||||
} else if (contextType === 'group') {
|
||||
items.push({
|
||||
text: formatMessage('Group Images'),
|
||||
value: 'instructure_group_image'
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
text: formatMessage('User Images'),
|
||||
|
@ -58,6 +64,10 @@ function doMenuItem(ed, value) {
|
|||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForImages', false, COURSE_PLUGIN_KEY)
|
||||
break
|
||||
case 'instructure_group_image':
|
||||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForImages', false, GROUP_PLUGIN_KEY)
|
||||
break
|
||||
case 'instructure_user_image':
|
||||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForImages', false, USER_PLUGIN_KEY)
|
||||
|
|
|
@ -27,6 +27,7 @@ const trayController = new TrayController()
|
|||
|
||||
const COURSE_PLUGIN_KEY = 'course_media'
|
||||
const USER_PLUGIN_KEY = 'user_media'
|
||||
const GROUP_PLUGIN_KEY = 'group_media'
|
||||
|
||||
function getMenuItems(ed) {
|
||||
const contextType = ed.settings.canvas_rce_user_context.type
|
||||
|
@ -43,6 +44,11 @@ function getMenuItems(ed) {
|
|||
text: formatMessage('Course Media'),
|
||||
value: 'instructure_course_media'
|
||||
})
|
||||
} else if (contextType === 'group') {
|
||||
items.push({
|
||||
text: formatMessage('Group Media'),
|
||||
value: 'instructure_group_media'
|
||||
})
|
||||
}
|
||||
items.push({
|
||||
text: formatMessage('User Media'),
|
||||
|
@ -60,6 +66,10 @@ function doMenuItem(ed, value) {
|
|||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForMedia', false, COURSE_PLUGIN_KEY)
|
||||
break
|
||||
case 'instructure_group_media':
|
||||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForMedia', false, GROUP_PLUGIN_KEY)
|
||||
break
|
||||
case 'instructure_user_media':
|
||||
ed.focus(true)
|
||||
ed.execCommand('instructureTrayForMedia', false, USER_PLUGIN_KEY)
|
||||
|
|
|
@ -46,17 +46,17 @@ function getTrayLabel(contentType, contentSubtype, contextType) {
|
|||
|
||||
switch (contentSubtype) {
|
||||
case 'images':
|
||||
return contentType === 'course_files'
|
||||
? formatMessage('Course Images')
|
||||
: formatMessage('User Images')
|
||||
if (contentType === 'course_files') return formatMessage('Course Images')
|
||||
if (contentType === 'group_files') return formatMessage('Group Images')
|
||||
return formatMessage('User Images')
|
||||
case 'media':
|
||||
return contentType === 'course_files'
|
||||
? formatMessage('Course Media')
|
||||
: formatMessage('User Media')
|
||||
if (contentType === 'course_files') return formatMessage('Course Media')
|
||||
if (contentType === 'group_files') return formatMessage('Group Media')
|
||||
return formatMessage('User Media')
|
||||
case 'documents':
|
||||
return contentType === 'course_files'
|
||||
? formatMessage('Course Documents')
|
||||
: formatMessage('User Documents')
|
||||
if (contentType === 'course_files') return formatMessage('Course Documents')
|
||||
if (contentType === 'group_files') return formatMessage('Group Documents')
|
||||
return formatMessage('User Documents')
|
||||
default:
|
||||
return formatMessage('Tray') // Shouldn't ever get here
|
||||
}
|
||||
|
@ -107,6 +107,13 @@ const FILTER_SETTINGS_BY_PLUGIN = {
|
|||
sortValue: 'date_added',
|
||||
sortDir: 'desc'
|
||||
},
|
||||
group_documents: {
|
||||
contextType: 'group',
|
||||
contentType: 'group_files',
|
||||
contentSubtype: 'documents',
|
||||
sortValue: 'date_added',
|
||||
sortDir: 'desc'
|
||||
},
|
||||
user_images: {
|
||||
contextType: 'user',
|
||||
contentType: 'user_files',
|
||||
|
@ -121,6 +128,13 @@ const FILTER_SETTINGS_BY_PLUGIN = {
|
|||
sortValue: 'date_added',
|
||||
sortDir: 'desc'
|
||||
},
|
||||
group_images: {
|
||||
contextType: 'group',
|
||||
contentType: 'group_files',
|
||||
contentSubtype: 'images',
|
||||
sortValue: 'date_added',
|
||||
sortDir: 'desc'
|
||||
},
|
||||
user_media: {
|
||||
contextType: 'user',
|
||||
contentType: 'user_files',
|
||||
|
@ -135,6 +149,13 @@ const FILTER_SETTINGS_BY_PLUGIN = {
|
|||
sortValue: 'date_added',
|
||||
sortDir: 'desc'
|
||||
},
|
||||
group_media: {
|
||||
contextType: 'group',
|
||||
contentType: 'group_files',
|
||||
contentSubtype: 'media',
|
||||
sortValue: 'date_added',
|
||||
sortDir: 'desc'
|
||||
},
|
||||
course_links: {
|
||||
contextType: 'course',
|
||||
contentType: 'links',
|
||||
|
@ -225,6 +246,10 @@ export default function CanvasContentTray(props) {
|
|||
contextType = 'user'
|
||||
contextId = props.containingContext.userId
|
||||
break
|
||||
case 'group_files':
|
||||
contextType = 'group'
|
||||
contextId = props.containingContext.contextId
|
||||
break
|
||||
case 'course_files':
|
||||
case 'links':
|
||||
contextType = props.contextType
|
||||
|
|
|
@ -73,6 +73,13 @@ function renderTypeOptions(contentType, contentSubtype, userContextType) {
|
|||
</option>
|
||||
)
|
||||
}
|
||||
if (userContextType === 'group' && contentType !== 'links' && contentSubtype !== 'all') {
|
||||
options.push(
|
||||
<option key="group_files" value="group_files" icon={IconFolderLine}>
|
||||
{fileLabelFromContext('group')}
|
||||
</option>
|
||||
)
|
||||
}
|
||||
options.push(
|
||||
<option key="user_files" value="user_files" icon={IconFolderLine}>
|
||||
{fileLabelFromContext(contentType === 'links' || contentSubtype === 'all' ? 'files' : 'user')}
|
||||
|
@ -182,7 +189,7 @@ Filter.propTypes = {
|
|||
/**
|
||||
* `contentType` is the primary filter setting (e.g. links, files)
|
||||
*/
|
||||
contentType: oneOf(['links', 'user_files', 'course_files']).isRequired,
|
||||
contentType: oneOf(['links', 'user_files', 'course_files', 'group_files']).isRequired,
|
||||
|
||||
/**
|
||||
* `onChange` is called when any of the Filter settings are changed
|
||||
|
|
|
@ -120,6 +120,21 @@ describe('RCE Plugins > CanvasContentTray', () => {
|
|||
await showTrayForPlugin('links')
|
||||
expect(getTrayLabel()).toEqual('Group Links')
|
||||
})
|
||||
|
||||
it('is labeled with "Group Images" when using the "images" content type', async () => {
|
||||
await showTrayForPlugin('group_images')
|
||||
expect(getTrayLabel()).toEqual('Group Images')
|
||||
})
|
||||
|
||||
it('is labeled with "Group Media" when using the "media" content type', async () => {
|
||||
await showTrayForPlugin('group_media')
|
||||
expect(getTrayLabel()).toEqual('Group Media')
|
||||
})
|
||||
|
||||
it('is labeled with "Group Documents" when using the "group_documents" content type', async () => {
|
||||
await showTrayForPlugin('group_documents')
|
||||
expect(getTrayLabel()).toEqual('Group Documents')
|
||||
})
|
||||
})
|
||||
|
||||
describe('content panel', () => {
|
||||
|
|
|
@ -134,6 +134,14 @@ describe('RCE Plugins > Filter', () => {
|
|||
expect(component.getByLabelText('Content Type').value).toEqual('Course Files')
|
||||
})
|
||||
|
||||
it('has "Group" options', () => {
|
||||
renderComponent({userContextType: 'group'})
|
||||
|
||||
selectContentType('Group Files')
|
||||
expect(currentFilterSettings.contentType).toEqual('group_files')
|
||||
expect(component.getByLabelText('Content Type').value).toEqual('Group Files')
|
||||
})
|
||||
|
||||
it('has "User" options', () => {
|
||||
renderComponent({userContextType: 'course'})
|
||||
|
||||
|
|
|
@ -414,6 +414,31 @@ describe MediaObjectsController do
|
|||
])
|
||||
end
|
||||
|
||||
it "will limit return to group media" do
|
||||
course_with_teacher_logged_in(active_all: true)
|
||||
gcat = @course.group_categories.create!(:name => "My Group Category")
|
||||
@group = Group.create!(:name => "some group", :group_category => gcat, :context => @course)
|
||||
mo1 = MediaObject.create!(:user_id => @user, :context => @group, :media_id => "in_group")
|
||||
|
||||
MediaObject.create!(:user_id => @user, :context => @course, :media_id => "in_course_with_att")
|
||||
@course.attachments.create!(:media_entry_id => "in_course_with_att", :uploaded_data => stub_png_data)
|
||||
|
||||
MediaObject.create!(:user_id => @user, :context => @user, :media_id => "not_in_course")
|
||||
|
||||
get 'index', params: {:group_id => @group.id, :exclude => ["sources", "tracks"]}
|
||||
|
||||
expect(json_parse(response.body)).to match_array([
|
||||
{
|
||||
"media_id"=>"in_group",
|
||||
"media_type"=>nil,
|
||||
"created_at"=>mo1.created_at.as_json,
|
||||
"title"=>"Untitled",
|
||||
"can_add_captions"=>true,
|
||||
"embedded_iframe_url"=>"http://test.host/media_objects_iframe/in_group"
|
||||
}
|
||||
])
|
||||
end
|
||||
|
||||
it "will sort by title" do
|
||||
course_with_teacher_logged_in
|
||||
MediaObject.create!(:user_id => @user, :context => @user, :media_id => "test", :title => "ZZZ")
|
||||
|
|
Loading…
Reference in New Issue