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:
Augusto Callejas 2020-09-18 12:19:09 -10:00
parent 9d54c80754
commit 59a5fb70e3
22 changed files with 457 additions and 31 deletions

View File

@ -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)

View File

@ -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 = []

View File

@ -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) =>

View File

@ -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

View File

@ -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

View File

@ -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]

View File

@ -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>

View File

@ -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>
)

View File

@ -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

View File

@ -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>

View File

@ -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

View File

@ -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?

View File

@ -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))

View File

@ -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()

View File

@ -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)

View File

@ -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()

View File

@ -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)

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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',