Enable usage rights on discussion topic files
closes OUT-3933 flag=usage_rights_discussion_topics If a course has usage rights enabled, then discussion topics and announcements will require usage rights set when a file is attached. If a user does not have manage_files permission, then the usage rights indicator won't appear, but a default set of usage rights will be applied. prerequisites: - in a course, enable copyright and license information on files under course settings - create teacher and student accounts - create a group in the course, and add the student test plan (before enabling feature flag): - confirm that when creating course discussions with file attachments as a teacher and student, copyright information is not set in the Files section - confirm the same with group discussions test plan: - enable the feature flag - for course discussions: - as a teacher, confirm that when creating a discussion, usage rights are required when attaching a file. confirm the usage rights settings are set by re-editing the discussion and viewing the file in the Files section - as a student, confirm that when creating a discussion, usage rights are not required when attaching a file, but when the topic is created, the file appears with a copyright setting in the Files section - for group discussions: - as a teacher and student, confirm that when creating a discussion, usage rights are required when attaching a file. confirm the usage rights settings are set by re-editing the discussion and viewing the file in the Files section Change-Id: I0dc6532f7d8188cf4f623275fcf8562f19585f1f Reviewed-on: https://gerrit.instructure.com/c/canvas-lms/+/248211 Reviewed-by: Pat Renner <prenner@instructure.com> QA-Review: Pat Renner <prenner@instructure.com> Tested-by: Service Cloud Jenkins <svc.cloudjenkins@instructure.com> Product-Review: Jody Sailor
This commit is contained in:
parent
9d54c80754
commit
59a5fb70e3
|
@ -46,12 +46,16 @@ export default {
|
|||
copyright: null,
|
||||
use_justification: null,
|
||||
|
||||
submit() {
|
||||
submit(deferSaveCallback = null) {
|
||||
const values = this.usageSelection.getValues()
|
||||
|
||||
// They didn't choose a copyright
|
||||
if (values.use_justification === 'choose') {
|
||||
$(this.usageSelection.usageRightSelection).errorBox(I18n.t('You must specify a usage right.'))
|
||||
$(this.usageSelection.usageRightSelection).errorBox(
|
||||
I18n.t('You must specify a usage right.'),
|
||||
null,
|
||||
'fixed'
|
||||
)
|
||||
return false
|
||||
}
|
||||
|
||||
|
@ -61,6 +65,11 @@ export default {
|
|||
license: values.cc_license
|
||||
}
|
||||
|
||||
if (deferSaveCallback) {
|
||||
deferSaveCallback(usageRightValue)
|
||||
return this.props.closeModal()
|
||||
}
|
||||
|
||||
const afterSet = (success, data) => {
|
||||
if (success) {
|
||||
updateModelsUsageRights(data, this.props.itemsToManage)
|
||||
|
|
|
@ -35,7 +35,13 @@ import $ from 'jquery'
|
|||
import Folder from '../../models/Folder'
|
||||
import filesEnv from '../modules/filesEnv'
|
||||
|
||||
export default function setUsageRights(items, usageRights, callback) {
|
||||
export default function setUsageRights(
|
||||
items,
|
||||
usageRights,
|
||||
callback,
|
||||
overrideContextId = null,
|
||||
overrideContextType = null
|
||||
) {
|
||||
let contextId, contextType, parentFolder
|
||||
if (
|
||||
filesEnv.contextType === 'users' &&
|
||||
|
@ -48,6 +54,13 @@ export default function setUsageRights(items, usageRights, callback) {
|
|||
;({contextType, contextId} = filesEnv)
|
||||
}
|
||||
|
||||
if (!contextType) {
|
||||
contextType = overrideContextType
|
||||
}
|
||||
if (!contextId) {
|
||||
contextId = overrideContextId
|
||||
}
|
||||
|
||||
const apiUrl = `/api/v1/${contextType}/${contextId}/usage_rights`
|
||||
const folder_ids = []
|
||||
const file_ids = []
|
||||
|
|
|
@ -39,6 +39,9 @@ import numberHelper from 'jsx/shared/helpers/numberHelper'
|
|||
import DueDateCalendarPicker from 'jsx/due_dates/DueDateCalendarPicker'
|
||||
import SisValidationHelper from '../../util/SisValidationHelper'
|
||||
import AssignmentExternalTools from 'jsx/assignments/AssignmentExternalTools'
|
||||
import FilesystemObject from 'compiled/models/FilesystemObject'
|
||||
import UsageRightsIndicator from 'jsx/files/UsageRightsIndicator'
|
||||
import setUsageRights from '../../react_files/utils/setUsageRights'
|
||||
import * as returnToHelper from '../../../jsx/shared/helpers/returnToHelper'
|
||||
import 'jqueryui/tabs'
|
||||
|
||||
|
@ -92,9 +95,29 @@ export default class EditView extends ValidatedFormView
|
|||
@assignment = @model.get("assignment")
|
||||
@initialPointsPossible = @assignment.pointsPossible()
|
||||
@dueDateOverrideView = options.views['js-assignment-overrides']
|
||||
@on 'success', =>
|
||||
@unwatchUnload()
|
||||
@redirectAfterSave()
|
||||
@on 'success', (xhr) =>
|
||||
if xhr.attachments?.length == 1
|
||||
usageRights = @attachment_model.get('usage_rights')
|
||||
if usageRights and !_.isEqual(@initialUsageRights(), usageRights)
|
||||
[contextType, contextId] = ENV.context_asset_string.split("_")
|
||||
@attachment_model.set('id', xhr.attachments[0].id)
|
||||
setUsageRights(
|
||||
[@attachment_model]
|
||||
usageRights
|
||||
(_success, _data) => {}
|
||||
contextId
|
||||
"#{contextType}s"
|
||||
).always(() =>
|
||||
@unwatchUnload()
|
||||
@redirectAfterSave()
|
||||
)
|
||||
else
|
||||
@unwatchUnload()
|
||||
@redirectAfterSave()
|
||||
else
|
||||
@unwatchUnload()
|
||||
@redirectAfterSave()
|
||||
@attachment_model = new FilesystemObject()
|
||||
super
|
||||
|
||||
@lockedItems = options.lockedItems || {}
|
||||
|
@ -207,6 +230,9 @@ export default class EditView extends ValidatedFormView
|
|||
|
||||
this
|
||||
|
||||
shouldRenderUsageRights: =>
|
||||
ENV.FEATURES.usage_rights_discussion_topics and ENV.USAGE_RIGHTS_REQUIRED and ENV.PERMISSIONS.manage_files
|
||||
|
||||
afterRender: =>
|
||||
@renderStudentTodoAtDate() if @$todoDateInput.length
|
||||
[context, context_id] = ENV.context_asset_string.split("_")
|
||||
|
@ -216,7 +242,37 @@ export default class EditView extends ValidatedFormView
|
|||
"assignment_edit",
|
||||
parseInt(context_id),
|
||||
parseInt(@assignment.id))
|
||||
@renderUsageRights() if @shouldRenderUsageRights()
|
||||
|
||||
initialUsageRights: =>
|
||||
if @model.get('attachments')
|
||||
@model.get('attachments')[0]?.usage_rights
|
||||
|
||||
renderUsageRights: =>
|
||||
[contextType, contextId] = ENV.context_asset_string.split("_")
|
||||
usage_rights = @initialUsageRights()
|
||||
if usage_rights and !@attachment_model.get('usage_rights')
|
||||
@attachment_model.set('usage_rights', usage_rights)
|
||||
props =
|
||||
suppressWarning: true
|
||||
hidePreview: true
|
||||
contextType: "#{contextType}s"
|
||||
contextId: contextId
|
||||
model: @attachment_model
|
||||
deferSave: (usageRights) =>
|
||||
@attachment_model.set('usage_rights', usageRights)
|
||||
@renderUsageRights()
|
||||
userCanManageFilesForContext: true # usage rights indicator wouldn't be rendered without manage_files permission
|
||||
userCanRestrictFilesForContext: false # disable the publish section of the usage rights modal
|
||||
usageRightsRequiredForContext: true # usage rights indicator wouldn't be rendered without usage rights required for this context
|
||||
modalOptions:
|
||||
isOpen: false
|
||||
openModal: (contents, afterClose) =>
|
||||
ReactDOM.render(contents, @$('#usage_rights_modal')[0])
|
||||
closeModal: () =>
|
||||
ReactDOM.unmountComponentAtNode(@$('#usage_rights_modal')[0])
|
||||
component = React.createElement(UsageRightsIndicator, props, null)
|
||||
ReactDOM.render(component, @$('#usage_rights_control')[0])
|
||||
|
||||
attachKeyboardShortcuts: =>
|
||||
if !ENV.use_rce_enhancements
|
||||
|
@ -402,7 +458,9 @@ export default class EditView extends ValidatedFormView
|
|||
else
|
||||
super
|
||||
|
||||
fieldSelectors: _.extend({},
|
||||
fieldSelectors: _.extend({
|
||||
usage_rights_control: '#usage_rights_control button'
|
||||
},
|
||||
AssignmentGroupSelector::fieldSelectors,
|
||||
GroupCategorySelector::fieldSelectors
|
||||
)
|
||||
|
@ -456,6 +514,9 @@ export default class EditView extends ValidatedFormView
|
|||
if @showConditionalRelease()
|
||||
crErrors = @conditionalReleaseEditor.validateBeforeSave()
|
||||
errors['conditional_release'] = crErrors if crErrors
|
||||
|
||||
if @shouldRenderUsageRights() and @$('#discussion_attachment_uploaded_data').val() != "" and !@attachment_model.get('usage_rights')
|
||||
errors['usage_rights_control'] = [{message: I18n.t('You must set usage rights')}]
|
||||
errors
|
||||
|
||||
_validateTitle: (data, errors) =>
|
||||
|
|
|
@ -182,8 +182,8 @@ export default class ValidatedFormView extends Backbone.View
|
|||
hideErrors: ->
|
||||
@$el.hideErrors()
|
||||
|
||||
onSaveSuccess: =>
|
||||
@trigger 'success', arguments...
|
||||
onSaveSuccess: (xhr) =>
|
||||
@trigger 'success', xhr, arguments...
|
||||
|
||||
onSaveFail: (xhr) =>
|
||||
errors = @parseErrorResponse xhr
|
||||
|
|
|
@ -217,7 +217,8 @@ class ApplicationController < ActionController::Base
|
|||
JS_ENV_SITE_ADMIN_FEATURES = [:cc_in_rce_video_tray, :featured_help_links, :rce_lti_favorites, :new_math_equation_handling].freeze
|
||||
JS_ENV_ROOT_ACCOUNT_FEATURES = [
|
||||
:direct_share, :assignment_bulk_edit, :responsive_awareness, :recent_history,
|
||||
:responsive_misc, :product_tours, :module_dnd, :files_dnd, :unpublished_courses, :bulk_delete_pages
|
||||
:responsive_misc, :product_tours, :module_dnd, :files_dnd, :unpublished_courses, :bulk_delete_pages,
|
||||
:usage_rights_discussion_topics
|
||||
].freeze
|
||||
JS_ENV_FEATURES_HASH = Digest::MD5.hexdigest([JS_ENV_SITE_ADMIN_FEATURES + JS_ENV_ROOT_ACCOUNT_FEATURES].sort.join(",")).freeze
|
||||
def cached_js_env_account_features
|
||||
|
|
|
@ -501,11 +501,21 @@ class DiscussionTopicsController < ApplicationController
|
|||
}
|
||||
}
|
||||
|
||||
usage_rights_required = @context.try(:usage_rights_required)
|
||||
include_usage_rights = usage_rights_required &&
|
||||
@context.root_account.feature_enabled?(:usage_rights_discussion_topics)
|
||||
unless @topic.new_record?
|
||||
add_discussion_or_announcement_crumb
|
||||
add_crumb(@topic.title, named_context_url(@context, :context_discussion_topic_url, @topic.id))
|
||||
add_crumb t :edit_crumb, "Edit"
|
||||
hash[:ATTRIBUTES] = discussion_topic_api_json(@topic, @context, @current_user, session, override_dates: false)
|
||||
hash[:ATTRIBUTES] = discussion_topic_api_json(
|
||||
@topic,
|
||||
@context,
|
||||
@current_user,
|
||||
session,
|
||||
override_dates: false,
|
||||
include_usage_rights: include_usage_rights
|
||||
)
|
||||
end
|
||||
(hash[:ATTRIBUTES] ||= {})[:is_announcement] = @topic.is_announcement
|
||||
hash[:ATTRIBUTES][:can_group] = @topic.can_group?
|
||||
|
@ -556,6 +566,10 @@ class DiscussionTopicsController < ApplicationController
|
|||
SECTION_LIST: sections.map { |section| { id: section.id, name: section.name } },
|
||||
ANNOUNCEMENTS_LOCKED: announcements_locked?,
|
||||
CREATE_ANNOUNCEMENTS_UNLOCKED: @current_user.create_announcements_unlocked?,
|
||||
USAGE_RIGHTS_REQUIRED: usage_rights_required,
|
||||
PERMISSIONS: {
|
||||
manage_files: @context.grants_right?(@current_user, session, :manage_files)
|
||||
}
|
||||
}
|
||||
|
||||
post_to_sis = Assignment.sis_grade_export_enabled?(@context)
|
||||
|
@ -1248,14 +1262,26 @@ class DiscussionTopicsController < ApplicationController
|
|||
@topic = DiscussionTopic.find(@topic.id)
|
||||
@topic.broadcast_notifications(prior_version)
|
||||
|
||||
include_usage_rights = @context.root_account.feature_enabled?(:usage_rights_discussion_topics) &&
|
||||
@context.try(:usage_rights_required)
|
||||
if @context.is_a?(Course)
|
||||
render :json => discussion_topic_api_json(@topic,
|
||||
@context,
|
||||
@current_user,
|
||||
session,
|
||||
{include_sections: true, include_sections_user_count: true})
|
||||
{
|
||||
include_sections: true,
|
||||
include_sections_user_count: true,
|
||||
include_usage_rights: include_usage_rights
|
||||
})
|
||||
else
|
||||
render :json => discussion_topic_api_json(@topic, @context, @current_user, session)
|
||||
render :json => discussion_topic_api_json(@topic,
|
||||
@context,
|
||||
@current_user,
|
||||
session,
|
||||
{
|
||||
include_usage_rights: include_usage_rights
|
||||
})
|
||||
end
|
||||
else
|
||||
errors = @topic.errors.as_json[:errors]
|
||||
|
@ -1438,6 +1464,7 @@ class DiscussionTopicsController < ApplicationController
|
|||
if attachment
|
||||
@attachment = @context.attachments.new
|
||||
Attachments::Storage.store_for_attachment(@attachment, attachment)
|
||||
set_default_usage_rights(@attachment)
|
||||
@attachment.save!
|
||||
@attachment.handle_duplicates(:rename)
|
||||
@topic.attachment = @attachment
|
||||
|
@ -1446,6 +1473,17 @@ class DiscussionTopicsController < ApplicationController
|
|||
end
|
||||
end
|
||||
|
||||
def set_default_usage_rights(attachment)
|
||||
return unless @context.root_account.feature_enabled?(:usage_rights_discussion_topics)
|
||||
return unless @context.try(:usage_rights_required)
|
||||
return if @context.grants_right?(@current_user, session, :manage_files)
|
||||
|
||||
attachment.usage_rights = @context.usage_rights.find_or_create_by(
|
||||
use_justification:'own_copyright',
|
||||
legal_copyright: ''
|
||||
)
|
||||
end
|
||||
|
||||
def child_topic
|
||||
extra_params = {}
|
||||
if params[:headless]
|
||||
|
|
|
@ -172,9 +172,11 @@ UsageRightsDialog.render = function() {
|
|||
<div ref={e => (this.form = e)} className="UsageRightsDialog__Content">
|
||||
<div>
|
||||
<div className="UsageRightsDialog__paddingFix grid-row">
|
||||
<div className="UsageRightsDialog__previewColumn col-xs-3">
|
||||
<DialogPreview itemsToShow={this.props.itemsToManage} />
|
||||
</div>
|
||||
{!this.props.hidePreview && (
|
||||
<div className="UsageRightsDialog__previewColumn col-xs-3">
|
||||
<DialogPreview itemsToShow={this.props.itemsToManage} />
|
||||
</div>
|
||||
)}
|
||||
<div className="UsageRightsDialog__contentColumn off-xs-1 col-xs-8">
|
||||
{this.renderDifferentRightsMessage()}
|
||||
{this.renderFileName()}
|
||||
|
@ -184,6 +186,8 @@ UsageRightsDialog.render = function() {
|
|||
use_justification={this.use_justification}
|
||||
copyright={this.copyright || ''}
|
||||
cc_value={this.cc_value}
|
||||
contextType={this.props.contextType}
|
||||
contextId={this.props.contextId}
|
||||
/>
|
||||
{this.renderAccessManagement()}
|
||||
</div>
|
||||
|
@ -201,7 +205,7 @@ UsageRightsDialog.render = function() {
|
|||
buttonRef={e => (this.saveButton = e)}
|
||||
variant="primary"
|
||||
type="submit"
|
||||
onClick={this.submit}
|
||||
onClick={() => this.submit(this.props.deferSave)}
|
||||
>
|
||||
{I18n.t('Save')}
|
||||
</Button>
|
||||
|
|
|
@ -33,7 +33,12 @@ export default class UsageRightsIndicator extends React.Component {
|
|||
userCanManageFilesForContext: PropTypes.bool.isRequired,
|
||||
userCanRestrictFilesForContext: PropTypes.bool.isRequired,
|
||||
usageRightsRequiredForContext: PropTypes.bool.isRequired,
|
||||
modalOptions: PropTypes.object.isRequired
|
||||
modalOptions: PropTypes.object.isRequired,
|
||||
contextType: PropTypes.string,
|
||||
contextId: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
hidePreview: PropTypes.bool,
|
||||
deferSave: PropTypes.func,
|
||||
suppressWarning: PropTypes.bool
|
||||
}
|
||||
|
||||
handleClick = event => {
|
||||
|
@ -44,6 +49,10 @@ export default class UsageRightsIndicator extends React.Component {
|
|||
closeModal={this.props.modalOptions.closeModal}
|
||||
itemsToManage={[this.props.model]}
|
||||
userCanRestrictFilesForContext={this.props.userCanRestrictFilesForContext}
|
||||
contextType={this.props.contextType}
|
||||
contextId={this.props.contextId}
|
||||
hidePreview={this.props.hidePreview}
|
||||
deferSave={this.props.deferSave}
|
||||
/>
|
||||
)
|
||||
this.props.modalOptions.openModal(contents, () => {
|
||||
|
@ -78,10 +87,12 @@ export default class UsageRightsIndicator extends React.Component {
|
|||
<button
|
||||
className="UsageRightsIndicator__openModal btn-link"
|
||||
onClick={this.handleClick}
|
||||
title={this.warningMessage}
|
||||
title={this.props.suppressWarning ? null : this.warningMessage}
|
||||
data-tooltip="top"
|
||||
>
|
||||
<span className="screenreader-only">{this.warningMessage}</span>
|
||||
{!this.props.suppressWarning && (
|
||||
<span className="screenreader-only">{this.warningMessage}</span>
|
||||
)}
|
||||
<i className="UsageRightsIndicator__warning icon-warning" />
|
||||
</button>
|
||||
)
|
||||
|
|
|
@ -77,6 +77,7 @@ class Group < ActiveRecord::Base
|
|||
after_update :clear_cached_short_name, :if => :saved_change_to_name?
|
||||
|
||||
delegate :time_zone, :to => :context
|
||||
delegate :usage_rights_required, to: :context
|
||||
|
||||
include StickySisFields
|
||||
are_sis_sticky :name
|
||||
|
|
|
@ -78,7 +78,7 @@
|
|||
{{#if canAttach}}
|
||||
<div class="control-group" style="margin-left: -75px">
|
||||
{{#if lockedItems.content}}
|
||||
<label class="control-label" {{#t "attachment"}}Attachment{{/t}}</label>
|
||||
<label class="control-label">{{#t "attachment"}}Attachment{{/t}}</label>
|
||||
{{else}}
|
||||
<label class="control-label"
|
||||
aria-label="{{#t}}Add Attachment{{/t}}"
|
||||
|
@ -108,6 +108,22 @@
|
|||
{{/unless}}
|
||||
</div>
|
||||
</div>
|
||||
{{#if ENV.FEATURES.usage_rights_discussion_topics }}
|
||||
{{#if ENV.USAGE_RIGHTS_REQUIRED }}
|
||||
{{#if ENV.PERMISSIONS.manage_files }}
|
||||
<div class="control-group" style="margin-left: -75px">
|
||||
<label class="control-label"
|
||||
for="usage_rights_control">
|
||||
{{#t}}Set usage rights{{/t}}
|
||||
</label>
|
||||
<div class="controls">
|
||||
<div id="usage_rights_control" />
|
||||
<div id="usage_rights_modal" />
|
||||
</div>
|
||||
</div>
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
</fieldset>
|
||||
|
||||
|
|
|
@ -42,3 +42,9 @@ account_level_mastery_scales:
|
|||
display_name: Account-level Mastery Scales
|
||||
description: Allows Account-level mastery scales and proficiency calculations,
|
||||
replacing per-outcome scales
|
||||
usage_rights_discussion_topics:
|
||||
state: hidden
|
||||
applies_to: RootAccount
|
||||
display_name: Usage Rights on Discussion Topics
|
||||
description: On courses with usage rights enabled, enforces usage rights
|
||||
selection on discussion topic file attachments
|
||||
|
|
|
@ -88,6 +88,7 @@ module Api::V1::DiscussionTopics
|
|||
# include_all_dates: include all dates associated with the discussion topic (default: false)
|
||||
# override_dates: if the topic is graded, use the overridden dates for the given user (default: true)
|
||||
# root_topic_fields: fields to fill in from root topic (if any) if not already present.
|
||||
# include_usage_rights: Optionally include usage rights of the topic's file attachment, if any (default: false)
|
||||
# root_topics- if you alraedy have the root topics to get the root_topic_data from, pass
|
||||
# them in. Useful if this is to be called repeatedly and you don't want to make a
|
||||
# db call each time.
|
||||
|
@ -158,7 +159,9 @@ module Api::V1::DiscussionTopics
|
|||
#
|
||||
# Returns a hash.
|
||||
def serialize_additional_topic_fields(topic, context, user, opts={})
|
||||
attachments = topic.attachment ? [attachment_json(topic.attachment, user)] : []
|
||||
attachment_opts = {}
|
||||
attachment_opts[:include] = ['usage_rights'] if opts[:include_usage_rights]
|
||||
attachments = topic.attachment ? [attachment_json(topic.attachment, user, {}, attachment_opts)] : []
|
||||
html_url = named_context_url(context, :context_discussion_topic_url,
|
||||
topic, include_host: true)
|
||||
url = if topic.podcast_enabled?
|
||||
|
|
|
@ -1132,7 +1132,7 @@ $.fn.formErrors = function(data_errors, options) {
|
|||
|
||||
// Pops up a small box containing the given message. The box is connected to the given form element, and will
|
||||
// go away when the element is selected.
|
||||
$.fn.errorBox = function(message, scroll) {
|
||||
$.fn.errorBox = function(message, scroll, override_position) {
|
||||
if (this.length) {
|
||||
const $obj = this,
|
||||
$oldBox = $obj.data('associated_error_box')
|
||||
|
@ -1150,11 +1150,15 @@ $.fn.errorBox = function(message, scroll) {
|
|||
}
|
||||
$.screenReaderFlashError(message)
|
||||
|
||||
const $box = $template
|
||||
let $box = $template
|
||||
.clone(true)
|
||||
.attr('id', '')
|
||||
.css('zIndex', $obj.zIndex() + 1)
|
||||
.appendTo('body')
|
||||
|
||||
if (override_position) {
|
||||
$box = $box.css('position', override_position)
|
||||
}
|
||||
$box.appendTo('body')
|
||||
|
||||
// If our message happens to be a safe string, parse it as such. Otherwise, clean it up. //
|
||||
$box.find('.error_text').html(htmlEscape(message))
|
||||
|
|
|
@ -58,6 +58,17 @@ QUnit.module('UsageRightsDialog', suiteHooks => {
|
|||
component = ReactDOM.render(<UsageRightsDialog {...props} />, $container)
|
||||
}
|
||||
|
||||
test('displays dialog preview', () => {
|
||||
mountComponent()
|
||||
strictEqual(component.form.querySelectorAll('.DialogPreview__container').length, 1)
|
||||
})
|
||||
|
||||
test('does not display dialog preview', () => {
|
||||
props.hidePreview = true
|
||||
mountComponent()
|
||||
strictEqual(component.form.querySelectorAll('.DialogPreview__container').length, 0)
|
||||
})
|
||||
|
||||
test('clicking the close button closes modal', () => {
|
||||
props.closeModal = sinon.spy()
|
||||
mountComponent()
|
||||
|
|
|
@ -85,6 +85,54 @@ test('handleClick opens a modal with UsageRightsDialog', () => {
|
|||
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(uRI).parentNode)
|
||||
})
|
||||
|
||||
test('displays publish warning', () => {
|
||||
const props = {
|
||||
model: new File({id: 4}),
|
||||
usageRightsRequiredForContext: true,
|
||||
userCanManageFilesForContext: true,
|
||||
modalOptions: {
|
||||
openModal() {}
|
||||
},
|
||||
suppressWarning: false
|
||||
}
|
||||
const uRI = TestUtils.renderIntoDocument(<UsageRightsIndicator {...props} />)
|
||||
equal(
|
||||
ReactDOM.findDOMNode(uRI).getAttribute('title'),
|
||||
'Before publishing this file, you must specify usage rights.',
|
||||
'has warning text'
|
||||
)
|
||||
equal(
|
||||
ReactDOM.findDOMNode(uRI).textContent,
|
||||
'Before publishing this file, you must specify usage rights.',
|
||||
'has warning text'
|
||||
)
|
||||
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(uRI).parentNode)
|
||||
})
|
||||
|
||||
test('suppresses publish warning', () => {
|
||||
const props = {
|
||||
model: new File({id: 4}),
|
||||
usageRightsRequiredForContext: true,
|
||||
userCanManageFilesForContext: true,
|
||||
modalOptions: {
|
||||
openModal() {}
|
||||
},
|
||||
suppressWarning: true
|
||||
}
|
||||
const uRI = TestUtils.renderIntoDocument(<UsageRightsIndicator {...props} />)
|
||||
notEqual(
|
||||
ReactDOM.findDOMNode(uRI).getAttribute('title'),
|
||||
'Before publishing this file, you must specify usage rights.',
|
||||
'has warning text'
|
||||
)
|
||||
notEqual(
|
||||
ReactDOM.findDOMNode(uRI).textContent,
|
||||
'Before publishing this file, you must specify usage rights.',
|
||||
'has warning text'
|
||||
)
|
||||
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(uRI).parentNode)
|
||||
})
|
||||
|
||||
QUnit.module('UsageRightsIndicator: Icon Classess & Screenreader text', {
|
||||
teardown() {
|
||||
ReactDOM.unmountComponentAtNode(ReactDOM.findDOMNode(this.uRI).parentNode)
|
||||
|
|
|
@ -372,6 +372,30 @@ QUnit.module(
|
|||
})
|
||||
)
|
||||
|
||||
QUnit.module('EditView - Usage Rights', {
|
||||
setup() {
|
||||
fakeENV.setup()
|
||||
ENV.FEATURES.usage_rights_discussion_topics = true
|
||||
ENV.USAGE_RIGHTS_REQUIRED = true
|
||||
ENV.PERMISSIONS.manage_files = true
|
||||
this.server = sinon.fakeServer.create({respondImmediately: true})
|
||||
sandbox.fetch.mock('http://api/folders?contextType=user&contextId=1', 200)
|
||||
sandbox.fetch.mock('path:/api/session', 200)
|
||||
},
|
||||
teardown() {
|
||||
this.server.restore()
|
||||
fakeENV.teardown()
|
||||
},
|
||||
editView() {
|
||||
return editView.apply(this, arguments)
|
||||
}
|
||||
})
|
||||
|
||||
test('renders usage rights control', function() {
|
||||
const view = this.editView({permissions: {CAN_ATTACH: true}})
|
||||
equal(view.$el.find('#usage_rights_control').length, 1)
|
||||
})
|
||||
|
||||
QUnit.module('EditView - ConditionalRelease', {
|
||||
setup() {
|
||||
fakeENV.setup()
|
||||
|
|
|
@ -314,6 +314,22 @@ RSpec.describe ApplicationController do
|
|||
end
|
||||
end
|
||||
|
||||
context "usage_rights_discussion_topics" do
|
||||
before(:each) do
|
||||
controller.instance_variable_set(:@domain_root_account, Account.default)
|
||||
end
|
||||
|
||||
it 'is false if the feature flag is off' do
|
||||
Account.default.disable_feature!(:usage_rights_discussion_topics)
|
||||
expect(controller.js_env[:FEATURES][:usage_rights_discussion_topics]).to be_falsey
|
||||
end
|
||||
|
||||
it 'is true if the feature flag is on' do
|
||||
Account.default.enable_feature!(:usage_rights_discussion_topics)
|
||||
expect(controller.js_env[:FEATURES][:usage_rights_discussion_topics]).to be_truthy
|
||||
end
|
||||
end
|
||||
|
||||
context "unpublished_courses" do
|
||||
before(:each) do
|
||||
controller.instance_variable_set(:@domain_root_account, Account.default)
|
||||
|
|
|
@ -997,6 +997,60 @@ describe DiscussionTopicsController do
|
|||
expect(controller.js_env).not_to have_key :dummy
|
||||
end
|
||||
end
|
||||
|
||||
context 'usage rights - teacher' do
|
||||
before { user_session(@teacher) }
|
||||
before :once do
|
||||
attachment_model
|
||||
@topic_with_file = @course.discussion_topics.create!(title: "some topic", attachment: @attachment)
|
||||
end
|
||||
|
||||
shared_examples_for 'no usage rights returned' do
|
||||
it 'does not return usage rights on discussion topic attachment' do
|
||||
get :edit, params: {course_id: @course.id, id: @topic_with_file.id}
|
||||
expect(assigns[:js_env][:DISCUSSION_TOPIC][:ATTRIBUTES]['attachments'][0].key?('usage_rights')).to be false
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'usage rights returned' do
|
||||
it 'returns usage rights on discussion topic attachment' do
|
||||
get :edit, params: {course_id: @course.id, id: @topic_with_file.id}
|
||||
expect(assigns[:js_env][:DISCUSSION_TOPIC][:ATTRIBUTES]['attachments'][0].key?('usage_rights')).to be true
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usage_rights_discussion_topics disabled' do
|
||||
before { @course.root_account.disable_feature!(:usage_rights_discussion_topics) }
|
||||
|
||||
context 'enabled on course' do
|
||||
before { @course.update!(usage_rights_required: true) }
|
||||
|
||||
include_examples 'no usage rights returned'
|
||||
end
|
||||
|
||||
context 'disabled on course' do
|
||||
before { @course.update!(usage_rights_required: false) }
|
||||
|
||||
include_examples 'no usage rights returned'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usage_rights_discussion_topics enabled' do
|
||||
before { @course.root_account.enable_feature!(:usage_rights_discussion_topics) }
|
||||
|
||||
context 'enabled on course' do
|
||||
before { @course.update!(usage_rights_required: true) }
|
||||
|
||||
include_examples 'usage rights returned'
|
||||
end
|
||||
|
||||
context 'disabled on course' do
|
||||
before { @course.update!(usage_rights_required: false) }
|
||||
|
||||
include_examples 'no usage rights returned'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'student planner' do
|
||||
|
@ -1428,6 +1482,58 @@ describe DiscussionTopicsController do
|
|||
json = JSON.parse response.body
|
||||
expect(json['assignment']['anonymous_peer_reviews']).to be_falsey
|
||||
end
|
||||
|
||||
context 'usage rights - student' do
|
||||
let(:data) { fixture_file_upload("docs/txt.txt", "text/plain", true) }
|
||||
|
||||
before { user_session(@student) }
|
||||
|
||||
shared_examples_for 'no usage rights set' do
|
||||
it 'does not return usage rights on discussion topic attachment' do
|
||||
post 'create', params: topic_params(@course, attachment: data), :format => :json
|
||||
expect(Attachment.last.reload.usage_rights).to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
shared_examples_for 'usage rights set' do
|
||||
it 'returns usage rights on discussion topic attachment' do
|
||||
post 'create', params: topic_params(@course, attachment: data), :format => :json
|
||||
expect(Attachment.last.reload.usage_rights).not_to be_nil
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usage_rights_discussion_topics disabled' do
|
||||
before { @course.root_account.disable_feature!(:usage_rights_discussion_topics) }
|
||||
|
||||
context 'enabled on course' do
|
||||
before { @course.update!(usage_rights_required: true) }
|
||||
|
||||
include_examples 'no usage rights set'
|
||||
end
|
||||
|
||||
context 'disabled on course' do
|
||||
before { @course.update!(usage_rights_required: false) }
|
||||
|
||||
include_examples 'no usage rights set'
|
||||
end
|
||||
end
|
||||
|
||||
context 'with usage_rights_discussion_topics enabled' do
|
||||
before { @course.root_account.enable_feature!(:usage_rights_discussion_topics) }
|
||||
|
||||
context 'enabled on course' do
|
||||
before { @course.update!(usage_rights_required: true) }
|
||||
|
||||
include_examples 'usage rights set'
|
||||
end
|
||||
|
||||
context 'disabled on course' do
|
||||
before { @course.update!(usage_rights_required: false) }
|
||||
|
||||
include_examples 'no usage rights set'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "PUT: update" do
|
||||
|
|
|
@ -856,4 +856,11 @@ describe Group do
|
|||
expect(users.first.id).to eq @user.id
|
||||
end
|
||||
end
|
||||
|
||||
describe 'usage_rights_required' do
|
||||
it 'returns true' do
|
||||
@course.update!(usage_rights_required: true)
|
||||
expect(@group.usage_rights_required).to be true
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -299,6 +299,53 @@ describe "discussions" do
|
|||
expect(topic.locked?).to be_falsey
|
||||
end
|
||||
end
|
||||
|
||||
context "usage rights" do
|
||||
before do
|
||||
course.root_account.enable_feature!(:usage_rights_discussion_topics)
|
||||
course.update!(usage_rights_required: true)
|
||||
end
|
||||
|
||||
it "should validate that usage rights are set" do
|
||||
get url
|
||||
_filename, fullpath, _data = get_file("testfile5.zip")
|
||||
f('input[name=attachment]').send_keys(fullpath)
|
||||
type_in_tiny('textarea[name=message]', 'file attachment discussion')
|
||||
f('#edit_discussion_form_buttons .btn-primary[type=submit]').click
|
||||
wait_for_ajaximations
|
||||
error_box = f("div[role='alert'] .error_text")
|
||||
expect(error_box.text).to eq 'You must set usage rights'
|
||||
end
|
||||
|
||||
it "sets usage rights on file attachment" do
|
||||
get url
|
||||
_filename, fullpath, _data = get_file("testfile1.txt")
|
||||
f('input[name=attachment]').send_keys(fullpath)
|
||||
f('#usage_rights_control button').click
|
||||
click_option(".UsageRightsSelectBox__container select", 'own_copyright', :value)
|
||||
f(".UsageRightsDialog__Footer-Actions button[type='submit']").click
|
||||
expect_new_page_load { f('.form-actions button[type=submit]').click }
|
||||
expect(topic.reload.attachment.usage_rights).not_to be_nil
|
||||
end
|
||||
|
||||
it "displays usage rights on file attachment" do
|
||||
usage_rights = @course.usage_rights.create!(
|
||||
legal_copyright: '(C) 2012 Initrode',
|
||||
use_justification: 'own_copyright'
|
||||
)
|
||||
file = @course.attachments.create!(
|
||||
display_name: 'hey.txt',
|
||||
uploaded_data: default_uploaded_data,
|
||||
usage_rights: usage_rights
|
||||
)
|
||||
file.usage_rights
|
||||
topic.attachment = file
|
||||
topic.save!
|
||||
|
||||
get url
|
||||
expect(element_exists?("#usage_rights_control i.icon-files-copyright")).to eq(true)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -162,7 +162,7 @@ module DiscussionsCommon
|
|||
def add_attachment_and_validate
|
||||
filename, fullpath, _data = get_file("testfile5.zip")
|
||||
f('input[name=attachment]').send_keys(fullpath)
|
||||
type_in_tiny('textarea[name=message]', 'file attachement discussion')
|
||||
type_in_tiny('textarea[name=message]', 'file attachment discussion')
|
||||
yield if block_given?
|
||||
expect_new_page_load { submit_form('.form-actions') }
|
||||
wait_for_ajaximations
|
||||
|
|
|
@ -41,7 +41,7 @@ describe 'RCE Next autosave feature', ignore_js_errors: true do
|
|||
"{\"autosaveTimestamp\": \"#{time}\", \"content\": \"#{content}\"}"
|
||||
end
|
||||
|
||||
def autosave_key(url = driver.current_url, textarea_id = 'discussion-topic-message8')
|
||||
def autosave_key(url = driver.current_url, textarea_id = 'discussion-topic-message10')
|
||||
"rceautosave:#{url}:#{textarea_id}"
|
||||
end
|
||||
|
||||
|
@ -71,7 +71,7 @@ describe 'RCE Next autosave feature', ignore_js_errors: true do
|
|||
create_and_edit_announcement
|
||||
|
||||
switch_to_html_view
|
||||
f('textarea#discussion-topic-message8').send_keys('html text')
|
||||
f('textarea#discussion-topic-message10').send_keys('html text')
|
||||
driver.navigate.refresh
|
||||
accept_alert
|
||||
wait_for_rce
|
||||
|
@ -81,7 +81,7 @@ describe 'RCE Next autosave feature', ignore_js_errors: true do
|
|||
driver.local_storage.clear
|
||||
end
|
||||
|
||||
it 'should prompt to restore autosaved conent' do
|
||||
it 'should prompt to restore autosaved content' do
|
||||
create_and_edit_announcement
|
||||
saved_content = driver.local_storage[autosave_key]
|
||||
assert(saved_content)
|
||||
|
@ -163,7 +163,7 @@ describe 'RCE Next autosave feature', ignore_js_errors: true do
|
|||
|
||||
# simulate a placeholder image
|
||||
switch_to_html_view
|
||||
f('textarea#discussion-topic-message8').send_keys(
|
||||
f('textarea#discussion-topic-message10').send_keys(
|
||||
"<div data-placeholder-for='someimage.jpg' style='width: 200px; height: 50px;'>svg spinner here</div>"
|
||||
)
|
||||
switch_to_editor_view
|
||||
|
@ -208,7 +208,7 @@ describe 'RCE Next autosave feature', ignore_js_errors: true do
|
|||
insert_tiny_text text
|
||||
end
|
||||
|
||||
def autosave_key(url = driver.current_url, textarea_id = 'discussion-topic-message8')
|
||||
def autosave_key(url = driver.current_url, textarea_id = 'discussion-topic-message10')
|
||||
"rceautosave:#{url}:#{textarea_id}"
|
||||
end
|
||||
it 'should not prompt to restore autosaved content if the RCE is hidden',
|
||||
|
|
Loading…
Reference in New Issue